我在研究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版本中,你可以使用:而不是"::"或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();
}

其他回答

关于::方法引用的作用,前面的回答相当完整。总而言之,它提供了一种在不执行方法(或构造函数)的情况下引用方法(或构造函数)的方法,并且在计算时,它创建了提供目标类型上下文的函数接口实例。

下面是两个示例,在使用::方法引用和不使用::方法引用的情况下查找ArrayList中具有最大值的对象。解释见下面的评论。


不使用::

import java.util.*;

class MyClass {
    private int val;
    MyClass (int v) { val = v; }
    int getVal() { return val; }
}

class ByVal implements Comparator<MyClass> {
    // no need to create this class when using method reference
    public int compare(MyClass source, MyClass ref) {
        return source.getVal() - ref.getVal();
    }
}

public class FindMaxInCol {
    public static void main(String args[]) {
        ArrayList<MyClass> myClassList = new ArrayList<MyClass>();
        myClassList.add(new MyClass(1));
        myClassList.add(new MyClass(0));
        myClassList.add(new MyClass(3));
        myClassList.add(new MyClass(6));

        MyClass maxValObj = Collections.max(myClassList, new ByVal());
    }
}

使用::

import java.util.*;

class MyClass {
    private int val;
    MyClass (int v) { val = v; }
    int getVal() { return val; }
}

public class FindMaxInCol {
    static int compareMyClass(MyClass source, MyClass ref) {
        // This static method is compatible with the compare() method defined by Comparator.
        // So there's no need to explicitly implement and create an instance of Comparator like the first example.
        return source.getVal() - ref.getVal();
    }

    public static void main(String args[]) {
        ArrayList<MyClass> myClassList = new ArrayList<MyClass>();
        myClassList.add(new MyClass(1));
        myClassList.add(new MyClass(0));
        myClassList.add(new MyClass(3));
        myClassList.add(new MyClass(6));

        MyClass maxValObj = Collections.max(myClassList, FindMaxInCol::compareMyClass);
    }
}

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

答案很简单:**::被称为方法引用。在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);

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

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

简短说明:

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

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的状态。

这是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)。

在旧的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();
}