我在研究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?


当前回答

通常,我们会使用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表达式的翻译)

其他回答

::被称为方法引用。它基本上是对单个方法的引用。也就是说,它通过名称引用一个现有的方法。

简短说明:

下面是一个引用静态方法的例子:

class Hey {
     public static double square(double num){
        return Math.pow(num, 2);
    }
}

Function<Double, Double> square = Hey::square;
double ans = square.apply(23d);

Square可以像对象引用一样传递,并在需要时触发。事实上,它可以很容易地作为对象的“正常”方法的引用使用,就像静态方法一样。例如:

class Hey {
    public double square(double num) {
        return Math.pow(num, 2);
    }
}

Hey hey = new Hey();
Function<Double, Double> square = hey::square;
double ans = square.apply(23d);

以上是一个功能接口。为了充分理解::,理解功能接口也很重要。简单地说,函数式接口就是只有一个抽象方法的接口。

功能性接口的例子包括Runnable、Callable和ActionListener。

上面的函数是一个函数接口,只有一个方法:apply。它需要一个参数并产生一个结果。


::s很棒的原因是:

方法引用是与lambda表达式(…)具有相同处理方式的表达式,但它们不是提供方法体,而是通过名称引用现有方法。

例如,而不是写lambda体

Function<Double, Double> square = (Double x) -> x * x;

你可以简单地

Function<Double, Double> square = Hey::square;

在运行时,这两个平方方法的行为完全相同。字节码可以是相同的,也可以不是相同的(尽管,对于上面的情况,生成了相同的字节码;编译上面的代码并用javap -c检查)。

唯一需要满足的主要标准是:您提供的方法应该与用作对象引用的函数接口的方法具有类似的签名。

以下内容是非法的:

Supplier<Boolean> p = Hey::square; // illegal

Square需要参数并返回double。Supplier中的get方法返回一个值,但它不接受参数。因此,这会导致一个错误。

方法引用是指功能接口的方法。(如前所述,函数接口只能有一个方法。)

再举一些例子:Consumer中的accept方法接受一个输入,但它不返回任何东西。

Consumer<Integer> b1 = System::exit;   // void exit(int status)
Consumer<String[]> b2 = Arrays::sort;  // void sort(Object[] a)
Consumer<String> b3 = MyProgram::main; // void main(String... args)

class Hey {
    public double getRandom() {
        return Math.random();
    }
}

Callable<Double> call = hey::getRandom;
Supplier<Double> call2 = hey::getRandom;
DoubleSupplier sup = hey::getRandom;
// Supplier is functional interface that takes no argument and gives a result

上面,getRandom不接受任何参数并返回一个double。因此,任何满足以下条件的功能接口都可以使用:不接受参数并返回double。

另一个例子:

Set<String> set = new HashSet<>();
set.addAll(Arrays.asList("leo","bale","hanks"));
Predicate<String> pred = set::contains;
boolean exists = pred.test("leo");

对于参数化类型:

class Param<T> {
    T elem;
    public T get() {
        return elem;
    }

    public void set(T elem) {
        this.elem = elem;
    }

    public static <E> E returnSame(E elem) {
        return elem;
    }
}

Supplier<Param<Integer>> obj = Param<Integer>::new;
Param<Integer> param = obj.get();
Consumer<Integer> c = param::set;
Supplier<Integer> s = param::get;

Function<String, String> func = Param::<String>returnSame;

方法引用可以有不同的风格,但基本上它们都意味着相同的事情,可以简单地可视化为lambdas:

一个静态方法(ClassName::methName) 特定对象的实例方法(instanceRef::methName) 特定对象的超方法(super::methName) 特定类型的任意对象的实例方法(ClassName::methName) 类构造函数引用(ClassName::new) 数组构造函数引用(TypeName[]::new)

有关进一步参考,请参阅Lambda的状态。

通常,我们会使用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表达式的翻译)

由于这里的许多答案都很好地解释了::行为,另外,我想澄清::操作符不需要与引用函数接口具有完全相同的签名,如果它用于实例变量。让我们假设我们需要一个类型为TestObject的BinaryOperator。传统的实现方式是这样的:

BinaryOperator<TestObject> binary = new BinaryOperator<TestObject>() {

        @Override
        public TestObject apply(TestObject t, TestObject u) {

            return t;
        }
    };

正如您在匿名实现中看到的,它需要两个TestObject参数并返回一个TestObject对象。为了通过使用::操作符来满足这个条件,我们可以从一个静态方法开始:

public class TestObject {


    public static final TestObject testStatic(TestObject t, TestObject t2) {
        return t;
    }
}

然后调用:

BinaryOperator<TestObject> binary = TestObject::testStatic;

它编译得很好。如果我们需要实例方法呢?让我们用一个实例方法更新TestObject:

public class TestObject {

    public final TestObject testInstance(TestObject t, TestObject t2) {
        return t;
    }

    public static final TestObject testStatic(TestObject t, TestObject t2) {
        return t;
    }
}

现在我们可以像下面这样访问实例:

TestObject testObject = new TestObject();
BinaryOperator<TestObject> binary = testObject::testInstance;

这段代码编译得很好,但下面的代码不行:

BinaryOperator<TestObject> binary = TestObject::testInstance;

我的Eclipse告诉我“不能从类型TestObject中对非静态方法testInstance(TestObject, TestObject)进行静态引用…”

很好。它是一个实例方法,但如果我们重载testInstance,如下所示:

public class TestObject {

    public final TestObject testInstance(TestObject t) {
        return t;
    }

    public final TestObject testInstance(TestObject t, TestObject t2) {
        return t;
    }

    public static final TestObject testStatic(TestObject t, TestObject t2) {
        return t;
    }
}

和电话:

BinaryOperator<TestObject> binary = TestObject::testInstance;

代码将正常编译。因为它将用一个参数而不是双参数调用testInstance。好的,那么两个参数发生了什么?让我们打印出来看看:

public class TestObject {

    public TestObject() {
        System.out.println(this.hashCode());
    }

    public final TestObject testInstance(TestObject t) {
        System.out.println("Test instance called. this.hashCode:" +
                            this.hashCode());
        System.out.println("Given parameter hashCode:" + t.hashCode());
        return t;
    }

    public final TestObject testInstance(TestObject t, TestObject t2) {
        return t;
    }

    public static final TestObject testStatic(TestObject t, TestObject t2) {
        return t;
    }
}

它将输出:

 1418481495
 303563356
 Test instance called. this.hashCode:1418481495
 Given parameter hashCode:303563356

好,JVM足够聪明,可以调用param1.testInstance(param2)。我们可以从其他资源中使用testInstance而不是TestObject吗?例如:

public class TestUtil {

    public final TestObject testInstance(TestObject t) {
        return t;
    }
}

和电话:

BinaryOperator<TestObject> binary = TestUtil::testInstance;

它只是不编译,编译器会说:“类型TestUtil没有定义testInstance(TestObject, TestObject)”。

因此,如果静态引用不是同一类型,编译器将查找它。那么多态性呢?如果我们删除最后的修饰符,并添加SubTestObject类:

public class SubTestObject extends TestObject {

    public final TestObject testInstance(TestObject t) {
        return t;
    }

}

和电话:

BinaryOperator<TestObject> binary = SubTestObject::testInstance;

它也不会编译。编译器仍然会查找静态引用。但是下面的代码可以很好地编译,因为它通过了is-a测试:

public class TestObject {

    public SubTestObject testInstance(Object t) {
        return (SubTestObject) t;
    }

}

BinaryOperator<TestObject> binary = TestObject::testInstance;

在运行时,它们的行为完全相同。字节码可能不相同(对于上面的情况,它生成相同的字节码(编译上面的代码并检查javaap -c;))。

在运行时,它们的行为完全相同。方法(math::max)生成相同的数学结果(编译上面的代码并检查javap -c;))。

这是Java 8中的一个方法引用。Oracle文档在这里。

如文件所述……

方法引用Person::compareByAge是对静态对象的引用 方法。 类的实例方法的引用示例 特定对象:

class ComparisonProvider {
    public int compareByName(Person a, Person b) {
        return a.getName().compareTo(b.getName());
    }

    public int compareByAge(Person a, Person b) {
        return a.getBirthday().compareTo(b.getBirthday());
    }
}

ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName); 

方法引用myComparisonProvider::compareByName调用方法compareByName 它是对象myComparisonProvider的一部分。JRE会推断 方法类型参数,在本例中为(Person, Person)。