:: (double colon) operator in Java 8

前端 未结 17 2831
旧时难觅i
旧时难觅i 2020-11-21 11:10

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

//defined in IntPipeline.java
@Override
public fin         


        
相关标签:
17条回答
  • 2020-11-21 11:39

    The :: is known as method references. Lets say we want to call a calculatePrice method of class Purchase. Then we can write it as:

    Purchase::calculatePrice
    

    It can also be seen as short form of writing the lambda expression Because method references are converted into lambda expressions.

    0 讨论(0)
  • 2020-11-21 11:41

    Since many answers here explained well :: behaviour, additionally I would like to clarify that :: operator doesnt need to have exactly same signature as the referring Functional Interface if it is used for instance variables. Lets assume we need a BinaryOperator which has type of TestObject. In traditional way its implemented like this:

    BinaryOperator<TestObject> binary = new BinaryOperator<TestObject>() {
    
            @Override
            public TestObject apply(TestObject t, TestObject u) {
    
                return t;
            }
        };
    

    As you see in anonymous implementation it requires two TestObject argument and returns a TestObject object as well. To satisfy this condition by using :: operator we can start with a static method:

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

    and then call:

    BinaryOperator<TestObject> binary = TestObject::testStatic;
    

    Ok it compiled fine. What about if we need an instance method? Lets update TestObject with instance method:

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

    Now we can access instance as below:

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

    This code compiles fine, but below one not:

    BinaryOperator<TestObject> binary = TestObject::testInstance;
    

    My eclipse tell me "Cannot make a static reference to the non-static method testInstance(TestObject, TestObject) from the type TestObject ..."

    Fair enough its an instance method, but if we overload testInstance as below:

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

    And call:

    BinaryOperator<TestObject> binary = TestObject::testInstance;
    

    The code will just compile fine. Because it will call testInstance with single parameter instead of double one. Ok so what happened our two parameter? Lets printout and see:

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

    Which will output:

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

    Ok so JVM is smart enough to call param1.testInstance(param2). Can we use testInstance from another resource but not TestObject, i.e.:

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

    And call:

    BinaryOperator<TestObject> binary = TestUtil::testInstance;
    

    It will just not compile and compiler will tell: "The type TestUtil does not define testInstance(TestObject, TestObject)". So compiler will look for a static reference if it is not the same type. Ok what about polymorphism? If we remove final modifiers and add our SubTestObject class:

    public class SubTestObject extends TestObject {
    
        public final TestObject testInstance(TestObject t){
            return t;
        }
    
    }
    

    And call:

    BinaryOperator<TestObject> binary = SubTestObject::testInstance;
    

    It will not compile as well, compiler will still look for static reference. But below code will compile fine since it is passing is-a test:

    public class TestObject {
    
        public SubTestObject testInstance(Object t){
            return (SubTestObject) t;
        }
    
    }
    
    BinaryOperator<TestObject> binary = TestObject::testInstance;
    

    *I am just studying so I have figured out by try and see, feel free to correct me if I am wrong

    0 讨论(0)
  • 2020-11-21 11:42

    :: Operator was introduced in java 8 for method references. A method reference is the shorthand syntax for a lambda expression that executes just ONE method. Here's the general syntax of a method reference:

    Object :: methodName
    

    We know that we can use lambda expressions instead of using an anonymous class. But sometimes, the lambda expression is really just a call to some method, for example:

    Consumer<String> c = s -> System.out.println(s);
    

    To make the code clearer, you can turn that lambda expression into a method reference:

    Consumer<String> c = System.out::println;
    
    0 讨论(0)
  • 2020-11-21 11:43

    The previous answers are quite complete regarding what :: method reference does. To sum up, it provides a way to refer to a method(or constructor) without executing it, and when evaluated, it creates an instance of the functional interface that provides the target type context.

    Below are two examples to find an object with the max value in an ArrayList WITH and WITHOUT the use of :: method reference. Explanations are in the comments below.


    WITHOUT the use of ::

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

    WITH the use of ::

    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);
        }
    }
    
    0 讨论(0)
  • 2020-11-21 11:44

    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("Fanishwar", "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). Lambda expression provides implementation of @FunctionalInterface so a functional interface can have only one abstract method. 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                       |
    +------------------------------------------------------------+--------------------------------------+
    
    0 讨论(0)
  • 2020-11-21 11:45

    I found this source very interesting.

    In fact, it is the Lambda that turns into a Double Colon. The Double Colon is more readable. We follow those steps:

    STEP1:

    // We create a comparator of two persons
    Comparator c = (Person p1, Person p2) -> p1.getAge().compareTo(p2.getAge());
    

    STEP2:

    // We use the interference
    Comparator c = (p1, p2) -> p1.getAge().compareTo(p2.getAge());
    

    STEP3:

    // The magic using method reference
    Comparator c = Comparator.comparing(Person::getAge);
    
    0 讨论(0)
提交回复
热议问题