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

从编译时到字节码生成的Java双冒号运算符?,java,lambda,java-8,Java,Lambda,Java 8,在本文中,作者使用了以下示例: @Override public final OptionalInt max() { return reduce(Math::max); //this is the gotcha line } 因此,在本例中,max()似乎是该类实例上Math.max的代理。但是,没有参数传递给max,所以java 8会将其编译为(伪代码): 还有,如何为类似这样的东西编写javadoc?在Java 8之前,应该编写为: public MyMathInteface {

在本文中,作者使用了以下示例:

@Override
public final OptionalInt max() {
    return reduce(Math::max); //this is the gotcha line
}
因此,在本例中,
max()
似乎是该类实例上
Math.max
的代理。但是,没有参数传递给max,所以java 8会将其编译为(伪代码):


还有,如何为类似这样的东西编写javadoc?

在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)负责调用该函数。

在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)负责调用该函数

因此,在本例中,
max()
似乎是该类实例上
Math.max
的代理。但是,没有参数传递给max,所以java 8会将其编译为(伪代码):

不完全是:)。让我们从计算
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);
stream.reduce(
    new IntBinaryOperator() {
        public int applyAsInt(int a, int b) {
            return b > a ? b : a;
        }
    }
);
您可以通过多种方式提供符合此规范的函数。在Java 8之前,您可能做过如下操作:

int applyAsInt(int left, int right);
stream.reduce(
    new IntBinaryOperator() {
        public int applyAsInt(int a, int b) {
            return b > a ? b : a;
        }
    }
);
Java8为我们提供了一种称为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
的包装类,该类在实现
applyAsInt
时委托给
Math.max
。它只执行此“引导”步骤一次。由于调用
Math.max
只依赖于传入的两个数字,因此它可以缓存该实现,并在下次使用此代码路径时使用它

因此,在本例中,
max()
似乎是该类实例上
Math.max
的代理。但是,没有参数传递给max,所以java 8会将其编译为(伪代码):

不完全是:)。让我们从计算
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);
stream.reduce(
    new IntBinaryOperator() {
        public int applyAsInt(int a, int b) {
            return b > a ? b : a;
        }
    }
);
您可以通过多种方式提供符合此规范的函数。在J之前