有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

Java 8 Streams map API解释方法参考

示例代码:

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]

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

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


共 (1) 个答案

  1. # 1 楼答案

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

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

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

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

    When the compiler encounters a lambda expression, it first lowers (desugars) the lambda body into a method whose argument list and return type match that of the lambda expression, possibly with some additional arguments (for values captured from the lexical scope, if any.) At the point at which the lambda expression would be captured, it generates an invokedynamic call site, which, when invoked, returns an instance of the functional interface to which the lambda is being converted. This call site is called the lambda factory for a given lambda. The dynamic arguments to the lambda factory are the values captured from the lexical scope. The bootstrap method of the lambda factory is a standardized method in the Java language runtime library, called the lambda metafactory. The static bootstrap arguments capture information known about the lambda at compile time (the functional interface to which it will be converted, a method handle for the desugared lambda body, information about whether the SAM type is serializable, etc.)

    Method references are treated the same way as lambda expressions, except that most method references do not need to be desugared into a new method; we can simply load a constant method handle for the referenced method and pass that to the metafactory

    Instance-capturing method reference forms include bound instance method references (s::length, captured with reference kind 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;
      

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

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

    If the compile-time declaration is an instance method, then the target reference is the first formal parameter of the invocation method. Otherwise, there is no target reference