【问题标题】:Why can I not assign a method reference directly to a variable of Object type?为什么我不能将方法引用直接分配给 Object 类型的变量?
【发布时间】:2015-03-16 16:48:01
【问题描述】:

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

?

【问题讨论】:

  • 在您的第一个不可编译的 sn-p 中,您希望在 of_ref 中存储什么?引用的实例的具体类型是什么?

标签: java syntax java-8


【解决方案1】:

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

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

【讨论】:

  • 谢谢,但我问为什么我不能直接将此引用分配给Object
  • 因为Object,在JLS引用中表示为T,不是函数式接口类型。
【解决方案2】:

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

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

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

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

public abstract void xxx(int value);

可以用作目标类型。那么,如果您将 lambda 分配给 Object 类型,您希望运行时实现哪个接口?这就是为什么您必须显式提供功能接口作为目标类型的原因。

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

【讨论】:

  • 谢谢,但这听起来不合逻辑:我可以将我的obj 转换回Function 并调用apply(),所以我们在检索方法引用时不能直接这样做吗?跨度>
  • @Andremoniy 要将obj 转换回Function,您必须首先创建一个object 对吗? object 的类型是什么?
  • 啊,好吧,我明白你的逻辑了。
  • 问题是编译器不知道将提供的 lambda 表达式或方法引用转换为哪个功能接口。例如,方法 ref String::isEmptyFunction<String,Boolean>Predicate<String> 和许多其他方法兼容。编译器通过查看赋值上下文来学习所需的函数类型; Object 在这里没有提供足够的信息。这与为什么你不能说new List<String>() 没有什么不同;编译器不知道你想要List 的哪个实现。
  • 请注意,添加强制转换足以让编译器推断出要实现的正确功能接口:Object obj = (Function<?, ?>) Stream::of
【解决方案3】:

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

考虑相同的问题但对于匿名类是有益的。尽管 lambda 和匿名类在实现上存在差异,但从语义上讲,lambda 本质上相当于匿名类的一个子集,一个 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);
    }
};

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

【讨论】:

  • 好的!这意味着它只是语法糖,是吗?
  • @Andremoniy:嗯,这取决于你如何定义“语法糖”。在某些情况下,需要进行一些转换(例如,lambda 中的this 等效于匿名类中的OuterClass.this,而不是this),并且存在一些行为差异,例如两个匿名类对象是不同的(!= ),但两个 lambda 可能不是。但在大多数情况下,您可以将其视为语法糖。
【解决方案4】:

你可以!你只需要给编译器更多的信息,让它知道方法引用应该实现什么功能接口:

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

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

【讨论】:

    【解决方案5】:

    我怀疑这是一个纯粹的学术问题,因为我看不到任何现实生活中的用例。尽管如此,我很确定这与 Stream::of 是一个 lambda 表达式有关。你也不能这样做:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-11-15
      • 2019-05-01
      • 2017-07-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多