Java双冒号运算符从编译时到字节代码生成?

时间:2017-09-14 19:13:56

标签: java lambda java-8

this question中,作者使用以下示例:

@Override
public final OptionalInt max() {
    return reduce(Math::max); //this is the gotcha line
}

因此,在这种情况下,max()似乎是此类实例上Math.max的代理。但是没有传递给max的参数,因此java 8将其编译为类似(伪代码):

@Override
public final OptionalInt max(Integer a, Integer b) {
      //If neither a or b are null
      return new OptionalInt.of(Math.max(a,b));
      //Otherwise return empty because we can't compare the numbers
      return OptionalInt.empty()
}

另外,如何为这样的东西编写javadoc?

2 个答案:

答案 0 :(得分:3)

  

因此,在这种情况下,max()似乎是此类实例上Math.max的代理。但是没有传递给max的参数,因此java 8将其编译为类似(伪代码):

@Override
public final OptionalInt max(Integer a, Integer b) {
      //If neither a or b are null
      return new OptionalInt.of(Math.max(a,b));
      //Otherwise return empty because we can't compare the numbers
      return OptionalInt.empty()
}

不完全:)。让我们首先弄清楚reduce运算符实际上做了什么。该文档解释了它通过应用逻辑上等同于以下的算法对一系列数字执行缩减

public OptionalInt reduce(IntBinaryOperator op) {
    boolean foundAny = false;
    int result = 0;

    for (int element : [this stream]) {
        if (!foundAny) {
            foundAny = true;
            result = element;
        }
        else {
            result = op.applyAsInt(result, element);
        }
    }

    return foundAny ? OptionalInt.of(result)
                    : OptionalInt.empty();
}

看起来很简单。如果您可以告诉它如何使用两个数字和'减少'或者'结合'将它们合二为一,然后reduce知道如何扩展该逻辑以将整个序列减少为单个数字。它为您处理边缘情况和聚合。它所需要的只是一个函数,它接收两个数字然后给它一个。该函数应符合函数接口IntBinaryOperator

功能接口是一个用于描述单个功能的接口。具体来说,它描述了参数类型和返回类型。其余的基本上是多余的。 IntBinaryOperator的签名如下所示:

int applyAsInt(int left, int right);

您可以通过多种方式提供符合此规范的功能。在Java 8之前,您可能已经做过类似的事情:

stream.reduce(
    new IntBinaryOperator() {
        public int applyAsInt(int a, int b) {
            return b > a ? b : a;
        }
    }
);

Java 8为我们提供了一个名为 lambda表达式的功能接口的简写形式。这些更简洁,虽然它们在概念上与匿名内部类相似,但它们并不完全相同。

stream.reduce((a, b) -> b > a ? b : a);

上述两个函数都是等价的:它们接收两个数字并返回两者中较大的一个。事实证明,每个标准编程库都具有完全相同的功能。在Java中,该函数是Math.max。因此,我不是自己写这个逻辑,而是委托Math.max

stream.reduce((a, b) -> Math.max(a, b));

但是等等!所有reduce想要的是一个函数,它接受两个数字并返回一个。 Math.max这样做,所以我甚至需要将它包裹在lambda中?原来我不知道;我可以告诉reduce直接致电Math.max

stream.reduce(Math::max);

这说"我知道你想要一个功能,所以我告诉你按名称在哪里找到一个已经写过的"。编译器知道Math.max符合我们需要的(int, int) -> int规范,因此它会发出一些字节码,告诉VM如何引导'一旦它需要它。当JVM点击您对reduce的调用时,它会调用一个特殊方法,该方法生成一个实现IntBinaryOperator的包装类,该类在Math.max的实现中委托给applyAsInt。它只执行这个' bootstrapping'一步。由于调用Math.max并不依赖于传入的两个数字以外的其他任何内容,因此它可以缓存该实现并在下次结束此代码路径时使用它。

答案 1 :(得分:2)

Pre Java 8,这可以写成:

public MyMathInteface {
    OptionalInt max(Integer a, Integer b);
}

public static final MyMathInterface reducing = new MyMathInterface() {
    @Override
    public OptionalInt max(Integer a, Integer b) {
        return OptionalInt.of(Math.max(a, b));
    }
};

@Override
public final OptionalInt max() {
    return reduce(reducing);
}

然后,reduce将被定义为:

public static OptionalInt reduce(MyMathInteface toReduce) {
    return toReduce.max(someValueA, someValueB);
}

因此,为了回答您的问题,没有参数传递给Math::max,因为这些值是由reduce函数检索的。它们可以是常数,也可以从其他地方检索。

在任何情况下,以这种方式使用max方法称为方法引用,即SomeObject::method::运算符创建方法引用。它返回一个函数,但不调用该函数。用户(reduce)负责调用该函数。