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, and Supplier 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