我在研究Java 8源代码时,发现这部分代码非常令人惊讶:

// Defined in IntPipeline.java
@Override
public final OptionalInt reduce(IntBinaryOperator op) {
    return evaluate(ReduceOps.makeInt(op));
}

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

// Defined in Math.java
public static int max(int a, int b) {
    return (a >= b) ? a : b;
}

max是一个方法指针吗?一个正常的静态方法如何转换为IntBinaryOperator?


当前回答

在Java 8中,Streams Reducer作为一个函数,它接受两个值作为输入,并在经过一些计算后返回结果。这个结果被输入到下一个迭代中。

在Math:max函数的情况下,该方法会不断返回传递的两个值中的最大值,最终您将得到最大的数字。

其他回答

双冒号,即::操作符,在Java 8中作为方法引用引入。方法引用是lambda表达式的一种形式,用于根据现有方法的名称引用该方法。

类名::methodName

例子:

流。forEach(System.out.println(element))

通过使用双冒号::

顺流而下。forEach(系统。出去::println(元素)

所以我在这里看到了大量的答案,坦率地说,它们过于复杂,这是一种保守的说法。

答案很简单:**::被称为方法引用。在Method References中,如果向下滚动到表格,可以找到所有的信息。


现在,让我们来简单了解一下什么是方法引用:

A::b在一定程度上替代了以下内联lambda表达式:(parameters…)-> A.b(parameter…)

要将此与您的问题联系起来,有必要理解Java lambda表达式。这并不难。

内联lambda表达式类似于已定义的函数接口(即具有不多于一个方法的接口)。

让我们来看看我的意思:

InterfaceX f = (x) -> x*x;

InterfaceX必须是功能接口。任何函数接口,对于编译器来说,InterfaceX唯一重要的是你定义了格式:

InterfaceX可以是以下任何一种:

interface InterfaceX
{
    public Integer callMe(Integer x);
}

或:

interface InterfaceX
{
    public Double callMe(Integer x);
}

或者更一般的说法:

interface InterfaceX<T, U>
{
    public T callMe(U x);
}

让我们以第一个例子和前面定义的内联lambda表达式为例。

在Java 8之前,你可以这样定义它:

 InterfaceX o = new InterfaceX(){
                        public int callMe(int x)
                        {
                            return x*x;
                        }
                    };

功能上是一样的。不同之处在于编译器如何感知它。

现在我们已经了解了内联lambda表达式,让我们返回到方法reference(::)。假设你有一个这样的类:

class Q {
    public static int anyFunction(int x)
    {
        return x + 5;
    }
}

由于方法anyFunctions与InterfaceX callMe具有相同的类型,我们可以用一个方法引用来等效这两个方法。

我们可以这样写:

InterfaceX o =  Q::anyFunction;

这就相当于:

InterfaceX o = (x) -> Q.anyFunction(x);

方法引用的一个很酷的优点是,在将它们分配给变量之前,它们都是无类型的。因此,您可以将它们作为参数传递给任何具有相同外观(具有相同定义类型)的函数接口。这正是你的情况。

在Java 8中,Streams Reducer作为一个函数,它接受两个值作为输入,并在经过一些计算后返回结果。这个结果被输入到下一个迭代中。

在Math:max函数的情况下,该方法会不断返回传递的两个值中的最大值,最终您将得到最大的数字。

通常,我们会使用Math调用reduce方法。Max (int, int)如下:

reduce(new IntBinaryOperator() {
    int applyAsInt(int left, int right) {
        return Math.max(left, right);
    }
});

这需要大量的语法来调用Math.max。这就是lambda表达式发挥作用的地方。因为Java 8允许它以更短的方式做同样的事情:

reduce((int left, int right) -> Math.max(left, right));

这是如何工作的呢?java编译器“检测”到你想要实现一个接受两个int型并返回一个int型的方法。这等价于接口IntBinaryOperator的唯一方法的形式参数(要调用的方法reduce的参数)。所以编译器会帮你完成剩下的工作——它只是假设你想要实现IntBinaryOperator。

但作为数学。max(int, int)本身满足IntBinaryOperator的形式要求,它可以直接使用。因为Java 7没有任何允许方法本身作为参数传递的语法(你只能传递方法结果,但不能传递方法引用),所以Java 8引入了::语法来引用方法:

reduce(Math::max);

注意,这将由编译器解释,而不是由JVM在运行时解释!虽然它为所有三个代码段生成了不同的字节码,但它们在语义上是相等的,因此后两个可以被认为是上面IntBinaryOperator实现的简短(而且可能更有效)版本!

(参见Lambda表达式的翻译)

在旧的Java版本中,你可以使用:而不是"::"或lambd:

public interface Action {
    void execute();
}

public class ActionImpl implements Action {

    @Override
    public void execute() {
        System.out.println("execute with ActionImpl");
    }

}

public static void main(String[] args) {
    Action action = new Action() {
        @Override
        public void execute() {
            System.out.println("execute with anonymous class");
        }
    };
    action.execute();

    //or

    Action actionImpl = new ActionImpl();
    actionImpl.execute();
}

或者传递给方法:

public static void doSomething(Action action) {
    action.execute();
}