:: (double colon) operator in Java 8
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 gets converted to IntBinaryOperator
?
Usually, one would call the reduce
method using Math.max(int, int)
as follows:
reduce(new IntBinaryOperator() {
int applyAsInt(int left, int right) {
return Math.max(left, right);
}
});
That requires a lot of syntax for just calling Math.max
. That's where lambda expressions come into play. Since Java 8 it is allowed to do the same thing in a much shorter way:
reduce((int left, int right) -> Math.max(left, right));
How does this work? The java compiler "detects", that you want to implement a method that accepts two int
s and returns one int
. This is equivalent to the formal parameters of the one and only method of interface IntBinaryOperator
(the parameter of method reduce
you want to call). So the compiler does the rest for you - it just assumes you want to implement IntBinaryOperator
.
But as Math.max(int, int)
itself fulfills the formal requirements of IntBinaryOperator
, it can be used directly. Because Java 7 does not have any syntax that allows a method itself to be passed as an argument (you can only pass method results, but never method references), the ::
syntax was introduced in Java 8 to reference methods:
reduce(Math::max);
Note that this will be interpreted by the compiler, not by the JVM at runtime! Although it produces different bytecodes for all three code snippets, they are semantically equal, so the last two can be considered to be short (and probably more efficient) versions of the IntBinaryOperator
implementation above!
(See also Translation of Lambda Expressions)
::
is called Method Reference. It is basically a reference to a single method. i.e. it refers to an existing method by name.
Short Explanation:
Below is an example of a reference to a static method:
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
can be passed around just like object references and triggered when needed. In fact, it can be just as easily used as a reference to "normal" methods of objects as static
ones. For example:
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);
Function
above is a functional interface. To fully understand ::
, it is important to understand functional interfaces as well. Plainly, a functional interface is an interface with just one abstract method.
Examples of functional interfaces include Runnable
, Callable
, and ActionListener
.
Function
above is a functional interface with just one method: apply
. It takes one argument and produces a result.
The reason why ::
s are awesome is that:
Method references are expressions which have the same treatment as lambda expressions (...), but instead of providing a method body, they refer an existing method by name.
E.g. instead of writing the lambda body
Function<Double, Double> square = (Double x) -> x * x;
You can simply do
Function<Double, Double> square = Hey::square;
At runtime, these two square
methods behave exactly the same as each other. The bytecode may or may not be the same (though, for the above case, the same bytecode is generated; compile the above and check with javap -c
).
The only major criterion to satisfy is: the method you provide should have a similar signature to the method of the functional interface you use as object reference.
The below is illegal:
Supplier<Boolean> p = Hey::square; // illegal
square
expects an argument and returns a double
. The get
method in Supplier returns a value but does not take an argument. Thus, this results in an error.
A method reference refers to the method of a functional interface. (As mentioned, functional interfaces can have only one method each).
Some more examples: the accept
method in Consumer takes an input but doesn't return anything.
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
Above, getRandom
takes no argument and returns a double
. So any functional interface that satisfies the criteria of: take no argument and return double
can be used.
Another example:
Set<String> set = new HashSet<>();
set.addAll(Arrays.asList("leo","bale","hanks"));
Predicate<String> pred = set::contains;
boolean exists = pred.test("leo");
In case of parameterized types:
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;
Method references can have different styles, but fundamentally they all mean the same thing and can simply be visualized as lambdas:
- A static method (
ClassName::methName
) - An instance method of a particular object (
instanceRef::methName
) - A super method of a particular object (
super::methName
) - An instance method of an arbitrary object of a particular type (
ClassName::methName
) - A class constructor reference (
ClassName::new
) - An array constructor reference (
TypeName[]::new
)
For further reference, see http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html.
Yes, that is true. The ::
operator is used for method referencing. So, one can extract static methods from classes by using it or methods from objects. The same operator can be used even for constructors. All cases mentioned here are exemplified in the code sample below.
The official documentation from Oracle can be found here.
You can have a better overview of the JDK 8 changes in this article. In the Method/Constructor referencing section a code example is also provided:
interface ConstructorReference {
T constructor();
}
interface MethodReference {
void anotherMethod(String input);
}
public class ConstructorClass {
String value;
public ConstructorClass() {
value = "default";
}
public static void method(String input) {
System.out.println(input);
}
public void nextMethod(String input) {
// operations
}
public static void main(String... args) {
// constructor reference
ConstructorReference reference = ConstructorClass::new;
ConstructorClass cc = reference.constructor();
// static method reference
MethodReference mr = cc::method;
// object method reference
MethodReference mr2 = cc::nextMethod;
System.out.println(cc.value);
}
}
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 |
+------------------------------------------------------------+--------------------------------------+