【问题标题】:Multiple lambda method references多个 lambda 方法引用
【发布时间】:2015-07-25 10:01:39
【问题描述】:

可以像这样链接/连接对 lambda 表达式中的元素所做的操作:

list.forEach(s -> {
        System.out.println(s.toLowerCase());
        System.out.println(s.toUpperCase());
});

有没有办法通过方法引用来做到这一点?像这样的:

list.forEach({
    System.out::println(String::toLowerCase);
    System.out::println(String::toCase);
});

我知道我可以在四个单独的调用中执行此操作(还可以执行更多操作,即改变值):

list.replaceAll(String::toLowerCase);
list.forEach(System.out::println);
list.replaceAll(String::toUpperCase);
list.forEach(System.out::println);

我什至不能做这样简单的事情:

list.forEach({
    System.out::println;
    System.out::println;
});

【问题讨论】:

  • 方法引用是一个表达式,它产生一个引用类型的值并且没有副作用。显然,尝试将这样的表达式用作块中的语句是没有意义的。老实说,我不知道你为什么想要那个——你只是喜欢在你的代码中看到一个双冒号而不是一个点吗?
  • 这不都是关于糖的吗?)Oracle MOOC 中说过要尽可能多地使用方法引用。但是看看这带来的复杂性我不会 - 只是它使代码更具可读性。

标签: java lambda java-8 method-reference


【解决方案1】:

可以通过功能接口的默认方法进行链接。但是“问题”在于,当您返回合成表达式的右侧时,推理引擎没有足够的信息来确定左侧是相同的功能接口。

要提供该信息,您必须转换声明:

  List<String> l = Collections.emptyList();
  l.forEach(((Consumer<String>)System.out::println).andThen(System.out::println));

或者先赋值给一个变量:

  Consumer<String> cons = System.out::println;
  Collections.<String>emptyList().forEach(cons.andThen(System.out::println));

或者,您也可以编写静态辅助方法来执行您想要的操作

Collections.<String>emptyList().forEach(combine(System.out::println, System.out::println));

static <T> Consumer<T> combine(Consumer<T>... consumers) {
    // exercise left to the reader
}

【讨论】:

  • 这个不行:list.forEach(((Consumer&lt;String&gt;)String::toUpperCase) .andThen(System.out::println));
  • 在我的机器上进行类型检查
  • 那么,有什么建议可以解决它并让它发挥作用吗?它只是不把单词变成大写。
  • 我回答了如何链接,而不是如何解决给出的确切样本。查看Function 接口了解其他链接方法。
【解决方案2】:

不,您不能按照您的建议使用方法引用。方法引用实际上只是 lambda 表达式的语法替换。所以,而不是:

text -> console.print(text)

您可以避免引入不必要的变量,而是使用

console::print

所以当你提到你不能做这样的事情时:

list.forEach({
    System.out::println;
    System.out::println;
});

这只是一个语法快捷方式

list.forEach({
    c -> System.out.println(c);
    c -> System.out.println(c);
});

这真的没有意义。列表中没有表示项目的变量(必须在块之外),并且两个“语句”是 lambda 表达式,没有任何应用。

方法引用是避免不必要变量的一种非常简洁的捷径,但它们只是替代更冗长的 lambda 表达式,不能用作块中的独立语句。

【讨论】:

    【解决方案3】:

    转换没有意义

    list.forEach(s -> {
        System.out.println(s.toLowerCase());
        System.out.println(s.toUpperCase());
    });
    

    list.forEach({
        System.out::println(String::toLowerCase);
        System.out::println(String::toUpperCase);
    });
    

    由于在清晰度上没有胜算,后者甚至比前者包含更多的字符,如果我们使用相同的缩进并插入Upper,您就省略了第二个变体。那么我们为什么要有这样一种替代形式呢?

    方法引用已被发明为允许单个方法委托的密集语法的功能,声明和引用参数可以真正产生影响。即使用System.out::println 替换唯一的s-&gt;System.out.println(s) 也不是什么大胜利,但至少有一些。此外,在字节码级别对方法引用进行编码可以更紧凑,因为可以直接引用目标方法,就像保存 lambda 表达式代码的合成方法一样。对于复合方法引用,没有这种紧凑的形式。


    由于您想要的操作由不同种类的操作组成,您可以使用Stream API,它旨在组合这些操作:

    list.stream().flatMap(s->Stream.of(s.toLowerCase(), s.toUpperCase()))
        .forEach(System.out::println);
    

    如果您想为所有内容包含方法引用,不惜一切代价,您可以通过以下方式进行:

    list.stream()
        .flatMap(s->Stream.<UnaryOperator<String>>of(String::toLowerCase, String::toUpperCase)
            .map(f->f.apply(s)))
        .forEach(System.out::println);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-11-18
      • 2019-05-11
      • 2014-10-25
      • 1970-01-01
      • 2018-10-03
      • 2021-12-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多