为什么我不能直接将方法引用分配给Object类型的变量?

关于java-8语法的简单问题。 为什么JLS-8限制这样的表达式:

 Object of_ref = Stream::of; // compile-time error 

并允许只有这样的东西:

 java.util.function.Function of_ref = Stream::of; Object obj = of_ref; // compiles ok 

那是因为方法引用或lambda表达式的目标类型应该是一个function接口。 仅基于此,运行时将创建提供给定function接口的实现的类的实例。 将lambdas或方法引用视为abstract 。 将其分配给function接口类型赋予其具体含义。

此外,特定的lambda或方法引用可以具有多个function接口作为其目标类型。 例如,考虑以下lamda:

 int x = 5; FunctionalInterface func = (x) -> System.out.println(x); 

这个lambda是xConsumer 。 除此之外,任何具有以下签名的抽象方法的接口:

 public abstract void xxx(int value); 

可以用作目标类型。 那么,如果将lambda分配给Object类型,那么您希望运行时实现哪个接口? 这就是为什么你要明确地提供一个function接口作为目标类型。

现在,一旦您获得了一个包含实例的function接口引用,您就可以将它分配给任何超级引用(包括Object

Object不是function接口,方法引用只能分配给function接口。 参见例如JLS#15.13.2

如果T是函数接口类型(第9.8节),并且表达式与从T派生的地面目标类型的函数类型一致,则方法引用表达式在赋值上下文,调用上下文或具有目标类型T的转换上下文中是兼容的。

关键是Java 中没有“函数类型” 。 lambda表达式本身没有“类型” – 它可以键入任何function接口,其唯一方法的签名与lambda匹配。 因此,lambda的类型基于其上下文提供的类型。 您必须提供一个function界面作为获取类型的上下文。

考虑同样的问题但对于匿名类是有益的。 尽管lambda和匿名类之间存在实现差异,但从语义上讲,lambdas本质上等同于匿名类的子集,并且lambda表达式总是可以转换为等效的匿名类创建表达式。

当你写:

 Function> of_ref = Stream::of; 

它相当于使用匿名类的以下内容:

 Function> of_ref = new Function>() { Stream apply(T t) { return Stream.of(t); } }; 

现在考虑

 Object of_ref = Stream::of; 

什么是匿名类的等价物?

 Object of_ref = new [**What goes here?**]() { [**What method signature goes here?**] { return Stream.of(t); } }; 

你明白为什么它没有意义 – 我们不知道使用什么类型作为匿名类的基类。

我怀疑这是一个纯粹的学术问题,因为我看不到任何真实的用例。 不过,我很确定它与Stream::of是一个lambda表达式有关。 你也可能不这样做:

 Object of_ref = list -> Stream.of(list); 

我推测一个准确的返回类型告诉编译器它正在使用哪个FunctionalInterface 。 如果没有这些信息,编译器就无法正确且明确地解析Lambda表达式。

您可以! 您只需要为编译器提供更多信息,以便它知道方法引用应该实现的function接口:

 Object obj = (Function) Stream::of; 

方法引用和lambda表达式通过使用类型推断来确定它们创建的匿名类应该实现什么接口。 如果没有Function ,Java必须使用的唯一类型是Object – 它肯定不是function接口(只有一个非静态非默认方法的接口)。 将方法引用表达式显式地转换为Function提供了我们需要的缺失类型信息,然后我们可以将Object字段分配给函数,因为FunctionObject的子类型。