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


当前回答

由于这里的许多答案都很好地解释了::行为,另外,我想澄清::操作符不需要与引用函数接口具有完全相同的签名,如果它用于实例变量。让我们假设我们需要一个类型为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中包含的一个新操作符,用于引用现有类的方法。可以引用类的静态方法和非静态方法。

对于引用静态方法,语法为:

ClassName :: methodName 

对于引用非静态方法,语法为

objRef :: methodName

And

ClassName :: methodName

引用方法的唯一前提是该方法存在于功能接口中,该接口必须与方法引用兼容。

计算方法引用时,将创建功能接口的实例。

这是在http://www.speakingcs.com/2014/08/method-references-in-java-8.html上找到的

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

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

lambda表达式用于创建匿名方法。它什么也不做,只是调用一个现有的方法,但是直接引用方法的名称会更清楚。方法引用允许我们使用方法引用操作符::来实现。

考虑下面的简单类,其中每个员工都有姓名和级别。

public class Employee {
    private String name;
    private String grade;

    public Employee(String name, String grade) {
        this.name = name;
        this.grade = grade;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGrade() {
        return grade;
    }

    public void setGrade(String grade) {
        this.grade = grade;
    }
}

假设我们有一个由某个方法返回的员工列表,并且我们希望根据员工的等级对其进行排序。我们知道我们可以使用匿名类作为:

    List<Employee> employeeList = getDummyEmployees();

    // Using anonymous class
    employeeList.sort(new Comparator<Employee>() {
           @Override
           public int compare(Employee e1, Employee e2) {
               return e1.getGrade().compareTo(e2.getGrade());
           }
    });

其中getDummyEmployee()是一个方法,如下:

private static List<Employee> getDummyEmployees() {
        return Arrays.asList(new Employee("Carrie", "C"),
                new Employee("Fanishwar", "F"),
                new Employee("Brian", "B"),
                new Employee("Donald", "D"),
                new Employee("Adam", "A"),
                new Employee("Evan", "E")
                );
    }

现在我们知道Comparator是一个功能接口。函数式接口是只有一个抽象方法的接口(尽管它可能包含一个或多个默认方法或静态方法)。Lambda表达式提供了@FunctionalInterface的实现,因此函数接口只能有一个抽象方法。我们可以这样使用lambda表达式:

employeeList.sort((e1,e2) -> e1.getGrade().compareTo(e2.getGrade())); // Lambda expression

看起来一切都很好,但是如果Employee类也提供类似的方法呢?

public class Employee {
    private String name;
    private String grade;
    // getter and setter
    public static int compareByGrade(Employee e1, Employee e2) {
        return e1.grade.compareTo(e2.grade);
    }
}

在这种情况下,使用方法名本身会更清楚。因此,我们可以通过使用方法引用直接引用该方法: employeeList.sort(员工::compareByGrade);//方法引用

根据文档,有四种方法引用:

+----+-------------------------------------------------------+--------------------------------------+
|    | Kind                                                  | Example                              |
+----+-------------------------------------------------------+--------------------------------------+
| 1  | Reference to a static method                          | ContainingClass::staticMethodName    |
+----+-------------------------------------------------------+--------------------------------------+
| 2  |Reference to an instance method of a particular object | containingObject::instanceMethodName |
+----+-------------------------------------------------------+--------------------------------------+
| 3  | Reference to an instance method of an arbitrary object| ContainingType::methodName           |
|    | of a particular type                                  |                                      |
+----+-------------------------------------------------------+--------------------------------------+
| 4  |Reference to a constructor                             | ClassName::new                       |
+------------------------------------------------------------+--------------------------------------+

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

类名::methodName

例子:

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

通过使用双冒号::

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