:: (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:22

In java-8 Streams Reducer in simple works is a function which takes two values as input and returns result after some calculation. this result is fed in next iteration.

in case of Math:max function, method keeps returning max of two values passed and in the end you have largest number in hand.

查看更多
有味是清欢
3楼-- · 2018-12-31 03:24

return reduce(Math::max); is NOT EQUAL to return reduce(max());

But it means, something like this:

IntBinaryOperator myLambda = (a, b)->{(a >= b) ? a : b};//56 keystrokes I had to type -_-
return reduce(myLambda);

You can just save 47 keystrokes if you write like this

return reduce(Math::max);//Only 9 keystrokes ^_^
查看更多
何处买醉
4楼-- · 2018-12-31 03:25

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
Comparator c = Comparator.comparing(Person::getAge());
查看更多
零度萤火
5楼-- · 2018-12-31 03:26

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

查看更多
孤独总比滥情好
6楼-- · 2018-12-31 03:31

:: is a new operator included in Java 8 that is used to refer a method of an existing class. You can refer static methods and non-static methods of a class.

For referring static methods, the syntax is:

ClassName :: methodName 

For referring non-static methods, the syntax is

objRef :: methodName

And

ClassName :: methodName

The only prerequisite for referring a method is that method exists in a functional interface, which must be compatible with the method reference.

Method references, when evaluated, create an instance of the functional interface.

Found on: http://www.speakingcs.com/2014/08/method-references-in-java-8.html

查看更多
谁念西风独自凉
7楼-- · 2018-12-31 03:31

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);
    }
}
查看更多
登录 后发表回答