What is the double colon (::) in Java? It's just a method reference!
::
is what we call a method reference, a reference to a single, existing method by name.
TL;DR. ::
can be used to extract methods that satisfy the criteria of FunctionalInterface
.
- Static method:
ClassName::methodName
- Instance method of an object:
instanceName::methodName
- Super method of an object:
super::methodName
- Instance method of an arbitrary object of a particular type:
ClassName::methodName
- Class constructor reference:
ClassName::new
- Array constructor reference:
TypeName[]::new
Let’s create a class CustomNumber
with a few methods.
Static method reference using ::
To start, suppose we have the static method addOne()
.
class CustomNumber {
public static int addOne(int num) {
return num + 1;
}
}
We can refer to the static method using the double colons.
Function<Integer, Integer> addOne = CustomNumber::addOne;
int result = addOne.apply(10);
Object method reference using ::
Let’s also add a non-static method addTwo()
.
class CustomNumber {
public int addTwo(int num) {
return num + 2;
}
}
We can refer to these normal, instance methods using the double colons.
CustomNumber num = new CustomNumber();
Function<Integer, Integer> addTwo = num::addTwo;
int result = addTwo.apply(10);
Functional interface basics
We might have noticed that we are storing these method references in a Function
, which is an example of a functional interface.
A functional interface is an interface with one abstract method, which we call the functional method.
Function
contains the functional method apply()
.
Runnable
,Callable
,ActionListener
, andSupplier
are all functional interfaces as well.
Lambda expressions (->
)
We know that with lambda expressions, we can define an anonymous method and treat it as an instance of a functional interface.
List<Integer> list = Arrays.asList(1, 2, 3);
Function<Integer, Integer> addOne = (Integer num) -> num + 1;
list.stream().map(addOne); // Valid
list.stream().map(num -> num + 1); // Valid
Method references (::
)
Method references are also functional interfaces, but they refer to an existing method by name.
List<Integer> list = Arrays.asList(1, 2, 3);
Function<Integer, Integer> addOne = CustomNumber::addOne;
list.stream().map(addOne); // Valid
list.stream().map(CustomNumber::addOne); // Valid
Functional interface criteria
The only criteria is: the referenced method should have the same signature as the target functional method.
In our scenario above, CustomNumber::addOne
accepts 1
argument and returns 1
result, which matches the signature of Function::apply
.
Suppose we have a method that returns a random integer.
class CustomNumber {
public static Integer getRand() {
return (int) Math.random();
}
}
Here, getRand()
accepts 0
arguments and returns 1
result, so we need a functional interface with a method whose signature matches (e.g. Supplier
).
Supplier<Integer> random = CustomNumber::getRand;
We can use the table below for quick insight into what function we should use.
Function | Accepts | Returns |
---|---|---|
Supplier |
() |
x |
Consumer |
x |
() |
BiConsumer |
x, y |
() |
Callable |
() |
x throws ex |
Runnable |
() |
() |
Function |
x |
y |
BiFunction |
x, y |
z |
Predicate |
x |
boolean |
UnaryOperator |
x1 |
x2 |
BinaryOperator |
x1, x2 |
x3 |