Java 8 Streams map API - 方法参考的解释

时间:2017-01-08 12:19:01

标签: java java-8 java-stream

示例代码:

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<T>功能界面。 [1]

  2. MyClass::instanceMethod接受Outer类型的参数,并且是Function<T,R>功能接口。 [1]

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

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

1 个答案:

答案 0 :(得分:4)

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

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

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

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

  

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

     

方法引用的处理方式与lambda表达式相同,   除了大多数方法参考不需要被贬低为a   新方法;我们可以简单地加载一个常量方法句柄   引用的方法并将其传递给metafactory

     

实例捕获方法参考表单包含绑定实例   方法引用(s :: length,用引用类型捕获   invokeVirtual

您的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;
    
  3. 注意,尽管第二行在第二种情况下更复杂,但最后一行是相同的。在这两种情况下,编译器只会看到一个带outer并返回Integer的函数。这符合map期望的内容。

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

      

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