【问题标题】:java 8 method reference into lambda [duplicate]java 8方法引用到lambda [重复]
【发布时间】:2018-03-02 20:52:12
【问题描述】:

我有这段代码,我尝试将方法引用 ("String::length") 转换为等效的 lambda 表达式。

 Stream<String> s = Stream.of("hello", "your name", "welcome", "z");

 List<String> list = s.sorted((a, b) -> Comparator.comparingInt(String::length).compare(a, b)).collect(toList());

 // List<String> list = s.sorted((a, b) -> Comparator.comparingInt( p -> {return ((String)p).length();}).compare(a, b)).collect(toList());

注释行中概述了它的唯一工作方式。我需要转换参数“p”。

似乎在编译时它指定参数“p”的类型是一个对象,如果我使用 lambda 表达式并且我需要显式转换。见下文:

<Object> Comparator<Object> java.util.Comparator.comparingInt(ToIntFunction<? super Object> keyExtractor)

当我使用 String::length 方法引用时,在编译时隐式参数被正确理解为 String 实例。在这种情况下有什么特别之处?见下文。

<String> Comparator<String> java.util.Comparator.comparingInt(ToIntFunction<? super String> keyExtractor)

【问题讨论】:

  • 这也应该有效(没有演员表):(String p) -&gt; p.length().
  • (a, b) -&gt; Comparator.comparingInt(String::length).compare(a, b) = Comparator.comparingInt(String::length)::compare = Comparator.comparingInt(String::length)
  • 我认为 OP 的问题是为什么在 p -&gt; p.length() 中,p 变量不会被 Java 编译器识别为 String
  • @tsolakp:是的,你是对的。
  • @Khanna11。我认为这是因为对comparingInt 的调用没有指定将传递给它的类型,默认情况下它假定一个对象。要给编译器一个提示,你需要使用方法参考、我之前注释过的语法或者这个:Comparator.&lt;String&gt;comparingInt( p -&gt; p.length() ).

标签: java sorting compiler-errors java-8 comparator


【解决方案1】:

不要使用匿名 lambda 来实现功能性 Comparator 接口,只需使用直接比较器:

List<String> list = 
    s.sorted(Comparator.comparingInt(String::length)).collect(toList());

【讨论】:

  • 我知道。问题是为什么“p”不被识别为字符串。
【解决方案2】:

编辑关于为什么p的类型没有被推断出来。

p 的类型不会自动推断为 String,原因与以下示例中未推断的原因非常相似:

String a = "a";
String b = "b";
Comparator.comparingInt(p -> p.length).compare(a, b);

在这里,它失败并显示Object 没有方法length 的消息。要理解为什么,考虑一下这个表达式的抽象语法树(非常粗略的近似):

                                                                  Apply
                                                                 /     \
                                                                /       \
                                                               /         \
                                                              /           \
                                             _______________ /             \__
                                            /                                 \
                                           /                                   \
                                          /                                     \
                                  MemberSelection                             ArgumentList(2)
                                  /             \______                       /             \
                                 /                     \                     /               \
                           Apply                      compare              a: String     b: String
                     _____/     \______
                    /                  \
                   /                    \
          MemberSelection             Lambda
          /          |                 |    \
         /           |                 |     \
Comparator     comparingInt       Variable    MemberSelection
                                       |          /      \
                                       p         p       length

如您所见,String 的类型信息完全位于 AST 的右侧,而变量 binder p 和整个闭包位于 AST 的左侧分支。

恰好类型推断算法总是以自上而下的方式在本地工作。一旦它下降到左子树并且无法推断p 的类型,它就不会返回树并在右子树中搜索其他提示。这实现起来太复杂了,类型检查器越远离有问题的绑定器p,关于类型推断失败的错误消息就越不清晰。类型推断算法不会对整个程序进行全局类型检查。


您根本不需要(a, b) -&gt; ... 部分,Comparator.compare(...) 已经生成了一个比较器:

List<String> list = s.
  sorted(Comparator.comparingInt(String::length)).
  collect(toList());

做你可能想做的事。

【讨论】:

  • 我知道。问题是为什么“p”不被识别为字符串。
  • @Khanna111 我已经添加了关于为什么在这种情况下类型推断算法失败的详细讨论。
  • @AndreyTyukin 我理解您所说的“从左到右”是什么意思,但这是对 Java 类型推断工作原理的一种相当误导的描述。但是您是正确的,在这种情况下——一个调用表达式a.b(),其中a 的类型具有类型变量——在我们知道这些变量之前,我们不能为b() 进行成员选择.因此,当链中的接收者是一个泛型方法调用并且我们不能仅从方法参数推断这些类型变量时,我们就会陷入困境。
  • @BrianGoetz 上面的草图并不是 javac 内部发生的事情的精确表示。我什至很确定真正的 AST 根本不包含任何 Apply 节点,因为 Java 不是 FP 语言,所以 Comparator.comparingInt 不会被视为函数。对于类 Java 语言的假设编译器来说,它是一个假设的 AST。那不是重点。关键是 pString 最终会出现在截然不同的子树中,无论你如何表示它,并且类型检查器在它甚至在另一个子树中看到 String 之前都无法推断出 p 的类型。
  • @BrianGoetz 在前面的评论中,“类 Java”我的意思是类型检查器不会从“左”子树返回一些奇怪的普遍量化的 forall a . Length&lt;a&gt; =&gt; Comparator&lt;a&gt;,然后愉快地继续搜索一些 Length&lt;String&gt;-"typeclass" 或类似的实例。
【解决方案3】:

String::length 的完整 lambda 为:

(String p) -> p.length()

也可以使用块来编写,但更常见的是使用上面更简单的表达式:

(String p) -> { return p.length(); }

如果编译器可以推断出类型,你可以省略它:

p -> p.length()

但你正在使用这个:

Comparator.comparingInt(p -> p.length())

这就是编译器在需要推断p 的类型时所看到的全部内容。那么,p 是什么?不知道,编译器说。所以你必须明确指定类型:

// Explicit type in parameter list
Comparator.comparingInt((String p) -> p.length())

// Explicit type on generic type parameter
Comparator.<String>comparingInt(p -> p.length())

// You can do both, but why would you?
Comparator.<String>comparingInt((String s) -> s.length())

// Explicit type of referenced method
Comparator.comparingInt(String::length)

请注意,没有任何代码使用强制转换。以上都是类型安全的,与您使用强制转换编写的代码不同。不要使用演员表!

以上所有 4 个调用都返回 Comparator&lt;String&gt;。该比较器可用于例如对List&lt;String&gt; 进行排序,但如果您尝试对任何其他类型的列表进行排序,则会出现编译错误。

当你这样投射时:

Comparator.comparingInt(p -> ((String) p).length())

它返回一个Comparator&lt;Object&gt;,这意味着您可以在尝试对任何类型的列表进行排序时给出该比较器,例如List&lt;Integer&gt;。它将编译,但在运行时失败。强制转换的使用使得代码不是类型安全的。正如我所说,不要那样做。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-11-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多