【问题标题】:Why lambda expression cannot be dereferenced?为什么不能取消引用 lambda 表达式?
【发布时间】:2015-12-09 21:32:19
【问题描述】:
import java.util.*;

class TreeMapDemo
{
    public static void main(String args[])
    {
        Comparator <String> c1 = (str1, str2) -> 0;

        Comparator <String> c2 = (str1, str2) -> 1;

        TreeMap <String, Double> tm1 = new TreeMap(c1.thenComparing(c2));
        //Working fine

        TreeMap <String, Double> tm2 = new TreeMap(((str1, str2) -> 0).thenComparing((str1, str2) -> 1));
        //Error: Lambda expression not expected here
        //<none> can not be dereferenced
    }
}

我的查询是:

如果

c1 = (str1, str2) -&gt; 0c2 = (str1, str2) -&gt; 1

那为什么

c1.thenComparing(c2) 工作正常,并且

((str1, str2) -&gt; 0).thenComparing((str1, str2) -&gt; 1)不是吗?

【问题讨论】:

  • Lambda 表达式不是比较器。 thenComparing 是一个比较器方法。
  • @alfasin : 如果不是比较器,那么它是如何与 TreeMap 构造函数映射的
  • 不是,你(应该)得到的编译错误是:“这里不需要 lambda 表达式”。您可能已经发现您可以将它转换为比较器,它会编译得很好(呃...)
  • 因为必须是 Comparator 类型的整个表达式,而不是调用 thenComparing 的对象。如果我扩展 BiFunction&lt;String, String, Integer&gt; 并添加一个返回 Comparator 的默认方法 thenComparing 它会变得模棱两可。
  • 除了您遇到的任何错误之外,始终返回 1 的 Comparator 不可能是正确的。比较器必须是对称的,这意味着如果 c.compare(x,y) 返回 1,那么 c.compare(y,x) 必须返回负数。此外,c.compare(x,x) 必须返回 0。如果您使用违反这些规则的 Comparator,并且您尝试在 sort 或其他内容中使用它,那么算法将失去理智。

标签: java lambda java-8 dereference dot-operator


【解决方案1】:

Lambda 表达式必须针对特定类型。

c1 = (str1, str2) -> 0;

没问题,因为c1 的类型是已知的。

(str1, str2) -&gt; 0 本身是模棱两可的。

例如

BiFunction<String, String, Integer> x = (str1, str2) -> 0;

也有道理。

要具体说明,请看这个例子。

public interface Foo extends BiFunction<String, String, Integer> {
    default Comparator<String> thenComparing(Comparator<String> comparator) {
        return String.CASE_INSENSITIVE_ORDER;
    }
}

public static void main(String[] args) {
    Foo foo = (str1, str2) -> 0;        // overriding the sole method of BiFunction
    TreeMap<String, Double> treeMap = new TreeMap<>(foo.thenComparing((str1, str2) -> 1));
}

这编译得很好。所以如果我们只写

new TreeMap<>((str1, str2) -> 0).thenComparing((str1, str2) -> 1));

编译器无法知道(str1, str2) -&gt; 0Foo 还是Comparator&lt;String&gt;。以这种方式推理不能向后工作。

【讨论】:

  • (str1, str2) -&gt; 0 定位到 Comparator 类型,因为 TreeMap 构造函数
  • 但是您期望这里有非常复杂的规则。可能有其他类具有返回ComparatorthenComparing 方法。您期望 Java 以 Comparator 开头,然后回顾所有可能返回 Comparator 的类和方法以推断原始 lambda 表达式的类型。
【解决方案2】:

这是 Java 中类型推断的限制。 首先,您使用原始类型,比较器将被解析为 Comparator 而不是 Comparator&lt;String&gt;

TreeMap <String, Double> tm1 = new TreeMap((str1, str2) -> 0);// wouldn't work

在哪里

TreeMap <String, Double> tm1 = new TreeMap<>((str1, str2) -> 0);// works

现在,您不能在类型被推断之前调用它的特定方法。

Comparator<String> c1 = ((str1, str2) -> 0).thenComparing((str1, str2) -> 1); // doesn't work

除非你为编译器提供一些额外的帮助

Comparator <String> c1 = ((Comparator<String>)((str1, str2) -> 0)).thenComparing((str1, str2) -> 1); // works

【讨论】:

  • 为什么人们坚持将“未能读懂我的想法”称为“类型推断的限制”?
  • @BrianGoetz 我不能不同意你的观点,Brian :) 但我确实澄清了为什么在推断类型之前无法解决该方法
【解决方案3】:

Java 依赖 lambda 表达式的上下文来确定其类型。将 lambda 分配给变量或将其作为方法参数传递可提供足够的上下文,但在 lambda 上调用方法(例如 thenComparing())则根本无法提供有用的上下文。

这应该可行:

Comparator <String> c1 = (str1, str2) -> 0;
TreeMap <String, Double> tm2 = new TreeMap(c1.thenComparing((str1, str2) -> 1));

更详细:

一个 lambda 表达式的计算结果是一个实现了某个功能接口的类型的对象,Java 依赖于表达式出现的上下文来确定 哪个 功能接口。在第二种情况下,构造函数参数不是 lambda 的上下文; thenComparing() 的调用是。该方法的返回值是构造函数参数。但是在 Java 可以确定关于thenComparing()任何事情之前,它需要知道调用它的对象的类型。类型通知方法,而不是相反。

Java 不能从参数类型向后工作到所需的 lambda 类型,因为可能有任意数量的函数式接口可以工作。 Java 不能假定想要的接口在标准库中。

【讨论】:

  • 多一点解释将不胜感激(主要是context
  • @kevingomes,根据要求提供更多详细信息。如果有具体的问题仍不清楚,请随时要求具体说明。
  • 您可能希望使用术语“独立表达式”来指代任何x.someMethod() 中的x。解析独立表达式时,编译器没有“目标类型”,因此无法为该位置的 lambda 确定功能接口类型。
猜你喜欢
  • 2023-04-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-16
  • 1970-01-01
  • 1970-01-01
  • 2013-09-06
  • 2014-06-21
相关资源
最近更新 更多