【问题标题】:Java 8 Streams map API - interpretation of method referenceJava 8 Streams map API - 方法参考的解释
【发布时间】:2017-11-27 10:26:56
【问题描述】:

示例代码:

class Outer {
    public Integer i;

    Outer(Integer i) {
        this.i = i;
    }

    public int getVal() { return i; }
}

class MyClass {

    public Integer f(Outer o) { return o.getVal();};

    public void main() {

        MyClass g = new MyClass();

        List<Integer> l1 = Arrays.asList(new Outer(2)).stream().map(g::f).collect(Collectors.toList());
        List<Integer> l2 = Arrays.asList(new Outer(2)).stream().map(Outer::getVal).collect(Collectors.toList());
    }
}

使用任一方法引用

  1. Outer::instanceMethod 不带参数,基本上是Supplier&lt;T&gt; 功能接口。 [1]

  2. MyClass::instanceMethod 接受 Outer 类型的参数并且是 Function&lt;T,R&gt;functional 接口。 [1]

有效。那么map 函数如何知道将选项(1)中的函数应用于流的对象,但将流对象传递给选项(2)中的函数?

[1]https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html

【问题讨论】:

  • 在每种情况下,它都是唯一可以编译的选项。尽管我不确定在哪里,但我确信 JLS 会详细说明。
  • 请注意,在这种情况下,outer::instanceMethod 不是供应商。它也是一个函数。函数的输入是this(即调用方法的outer实例),输出是方法返回的值。
  • @JBNizet 我怀疑这种行为。例如,在 Python 中,默认情况下,所有类函数都将 this 作为第一个参数传递,因此没有函数具有“零”参数。但是文档中哪里提到了这种行为/约定?
  • 附带说明,Arrays.asList(new outer(2)).stream() 以非常复杂的方式创建单个元素流,将实例包装在数组中,将数组包装在列表中,最终从列表创建流.只需改用Stream.of(new outer(2))

标签: java java-8 java-stream


【解决方案1】:

首先map 方法不知道自己如何处理方法引用。那是编译器的工作。在这两种情况下,map 都期望:

Function<? super PackageName.outer,? extends Integer>

对于您的特殊问题,根据docs 的两个方法引用都是对特定对象的实例方法的引用

关于编译器如何处理 lambda 和方法引用并将它们转换为字节码this document,强烈推荐阅读。与您的问题最相关的部分(强调我的总结):

当编译器遇到 lambda 表达式时,它首先降低 (desugars) 将 lambda 主体转换为一个方法,其参数列表和 返回类型与 lambda 表达式匹配,可能有一些 附加参数(对于从词法范围捕获的值,如果 任何。)在捕获 lambda 表达式的位置, 它会生成一个 invokedynamic 调用站点,调用该站点时会返回 lambda 所在的功能接口的一个实例 转换。此调用站点被称为 lambda 工厂 拉姆达。 lambda 工厂的动态参数是值 从词法范围中捕获。 lambda 的引导方法 factory 是 Java 语言运行时库中的一种标准化方法, 称为 lambda 元工厂。静态引导参数捕获 编译时已知的有关 lambda 的信息(函数 它将被转换到的接口,一个方法句柄 脱糖 lambda 主体,有关 SAM 类型是否为 可序列化等)

方法引用的处理方式与 lambda 表达式相同, 除了大多数方法引用不需要脱糖成 新方法;我们可以简单地为 引用的方法并将其传递给元工厂

实例捕获方法参考形式包括绑定实例 方法引用(s::length,使用 reference kind 捕获 调用虚拟)

您的 2 个案例的字节码是:

  1. outer::instanceMethod

    // handle kind 0x5 : INVOKEVIRTUAL
    PackageName/outer.getVal()I, 
    (LPackageName/outer;)Ljava/lang/Integer;
    
  2. MyClass::instanceMethod

    // handle kind 0x5 : INVOKEVIRTUAL
    PackageName/MyClass.f(LPackageName/outer;)Ljava/lang/Integer;, 
    (LPackageName/outer;)Ljava/lang/Integer;
    

注意,虽然第二行在第二种情况下更复杂,但最后一行是一样的。在这两种情况下,编译器只会看到一个接受outer 并返回Integer 的函数。这符合map 的预期。

方法参考在语言规范15.13 Method Reference Expressions 中进行了描述。 15.13.3 Run-Time Evaluation of Method References 中提到了方法引用的目标引用是方法的隐式第一个参数这一事实。

如果编译时声明是一个实例方法,那么目标 引用是调用方法的第一个形参。 否则,没有目标引用

【讨论】:

  • 我知道这两个函数最终都会传递一个outer 类型的对象作为参数。我想知道的是这种行为是否记录在任何地方,例如,类的 getter 隐式传递调用它们的对象实例。换句话说,编译器是否将outer.getValue() 解释为outer.getValue(outer this)
  • 您可以说编译器通常会这样做。我将在我的答案中添加对文档的引用
猜你喜欢
  • 1970-01-01
  • 2018-05-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-19
  • 1970-01-01
  • 2013-11-19
  • 1970-01-01
相关资源
最近更新 更多