:: (double colon) operator in Java 8

2018-12-31 02:40发布

I was exploring the Java 8 source and found this particular part of code very surprising:

//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;
}

Is Math::max something like a method pointer? How does a normal static method get converted to IntBinaryOperator?

标签: java java-8
16条回答
妖精总统
2楼-- · 2018-12-31 03:34

It seems its little late but here are my two cents. A lambda expression is used to create anonymous methods. It does nothing but call an existing method, but it is clearer to refer to the method directly by its name. And method reference enables us to do that using method-reference operator :: .

Consider the following simple class where each employee has a name and grade.

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;
    }
}

Suppose we have a list of employees returned by some method and we want to sort the employees by their grade. We know we can make use of anonymous class as:

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

where getDummyEmployee() is some method as:

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

Now we know that Comparator is a Functional Interface. A Functional Interface is the one with exactly one abstract method (though it may contain one or more default or static methods). So we can use lambda expression as:

employeeList.sort((e1,e2) -> e1.getGrade().compareTo(e2.getGrade())); // lambda exp

It seems all good but what if the class Employee also provides similar method:

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);
    }
}

In this case using the method name itself will be more clear. Hence we can directly refer to method by using method reference as:

employeeList.sort(Employee::compareByGrade); // method reference

As per docs there are four kinds of method references:

+----+-------------------------------------------------------+--------------------------------------+
|    | 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                       |
+------------------------------------------------------------+--------------------------------------+
查看更多
唯独是你
3楼-- · 2018-12-31 03:34

At runtime they behave a exactly the same.The bytecode may/not be same (For above Incase,it generates the same bytecode(complie above and check javaap -c;))

At runtime they behave a exactly the same.method(math::max);,it generates the same math (complie above and check javap -c;))

查看更多
路过你的时光
4楼-- · 2018-12-31 03:36

This is a method reference in Java 8. The oracle documentation is here.

As stated in the documentation...

The method reference Person::compareByAge is a reference to a static method.

The following is an example of a reference to an instance method of a particular object:

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); 

The method reference myComparisonProvider::compareByName invokes the method compareByName that is part of the object myComparisonProvider. The JRE infers the method type arguments, which in this case are (Person, Person).

查看更多
还给你的自由
5楼-- · 2018-12-31 03:36

So I see here tons of answers that are frankly overcomplicated, and that's an understatement.

The answer is pretty simple: :: it's called a Method References https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html

So I won't copy-paste, on the link, you can find all the information if you scroll down to the table.


Now, let's take a short look at what is a Method References:

A::B somewhat substitutes the following inline lambda expression: (params ...) -> A.B(params ...)

To correlate this with your questions, it's necessary to understand a java lambda expression. Which is not hard.

An inline lambda expression is similar to a defined functional interface (which is an interface that has no more and no less than 1 method). Let's take a short look what I mean:

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

InterfaceX must be a functional interface. Any functional interface, the only thing what's important about InterfaceX for that compiler is that you define the format:

InterfaceX can be any of this:

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

or this

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

or more generic:

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

Let's take the first presented case and the inline lambda expression that we defined earlier.

Before Java 8, you could've defined it similarly this way:

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

Functionally, it's the same thing. The difference is more in how the compiler perceives this.

Now that we took a look at inline lambda expression, let's return to Method References (::). Let's say you have a class like this:

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

Since method anyFunctions has the same types as InterfaceX callMe, we can equivalate those two with a Method Reference.

We can write it like this:

InterfaceX o =  Q::anyFunction; 

and that is equivalent to this :

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

A cool thing and advantage of Method References are that at first, until you assign them to variables, they are typeless. So you can pass them as parameters to any equivalent looking (has same defined types) functional interface. Which is exactly what happens in your case

查看更多
登录 后发表回答