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

时间:2015-03-16 16:48:02

标签: java syntax java-8

关于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

5 个答案:

答案 0 :(得分:9)

Object不是功能接口,方法引用只能分配给功能接口。请参阅示例JLS #15.13.2

  

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

答案 1 :(得分:8)

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

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

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

此lambda是Consumer的{​​{1}}。除此之外,任何具有以下签名的抽象方法的接口:

x

可用作目标类型。那么,如果将lambda分配给public abstract void xxx(int value); 类型,那么您希望运行时实现哪个接口?这就是为什么你要明确地提供一个功能接口作为目标类型。

现在,一旦您获得了一个包含实例的功能界面引用,您就可以将其分配给任何超级引用(包括Object

答案 2 :(得分:7)

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

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

当你写:

Function<T, Stream<T>> of_ref = Stream::of;

它等同于使用匿名类的以下内容:

Function<T, Stream<T>> of_ref = new Function<T, Stream<T>>() {
    Stream<T> 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);
    }
};

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

答案 3 :(得分:3)

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

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

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

答案 4 :(得分:2)

你可以!你只需要给编译器一些信息,这样它就知道方法引用应该实现什么功能接口:

Object obj = (Function<?, ?>) Stream::of;

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