1. Overview

In this quick article, we'll discuss the double colon operator ( :: ) in Java 8 and go over the scenarios where the operator can be used.

2. From Lambdas to Double Colon Operator

With Lambdas expressions, we've seen that code can become very concise.

For example, to create a comparator, the following syntax is enough:

Comparator c = (Computer c1, Computer c2) -> c1.getAge().compareTo(c2.getAge());

Then, with type inference:

Comparator c = (c1, c2) -> c1.getAge().compareTo(c2.getAge());

But can we make the code above even more expressive and readable? Let's have a look:

Comparator c = Comparator.comparing(Computer::getAge);

We've used the :: operator as shorthand for lambdas calling a specific method – by name. And the end, result is of course even more readable syntax.

3. How Does It Work?

Very simply put, when we are using a method reference – the target reference is placed before the delimiter :: and the name of the method is provided after it.

For example:

Computer::getAge;

We're looking at a method reference to the method getAge defined in the Computer class.

We can then operate with that function:

Function<Computer, Integer> getAge = Computer::getAge;
Integer computerAge = getAge.apply(c1);

Notice that we're referencing the function – and then applying it to the right kind of argument.

4. Method References

We can make good use of this operator in quite some scenarios.

4.1. A Static Method

First, we're going to make use of a static utility method:

List inventory = Arrays.asList(
  new Computer( 2015, "white", 35), new Computer(2009, "black", 65));
inventory.forEach(ComputerUtils::repair);

4.2. An Instance Method of an Existing Object

Next, let's have a look at an interesting scenario – referencing a method of an existing object instance.

We're going to use the variable System.out – an object of type PrintStream which supports the print method:

Computer c1 = new Computer(2015, "white");
Computer c2 = new Computer(2009, "black");
Computer c3 = new Computer(2014, "black");
Arrays.asList(c1, c2, c3).forEach(System.out::print);

4.3. An Instance Method of an Arbitrary Object of a Particular Type

Computer c1 = new Computer(2015, "white", 100);
Computer c2 = new MacbookPro(2009, "black", 100);
List inventory = Arrays.asList(c1, c2);
inventory.forEach(Computer::turnOnPc);

As you can see, we're referencing the turnOnPc method not on a specific instance, but on the type itself.

At line 4 the instance method turnOnPc will be called for every object of inventory.

And this naturally means that – for c1 the method turnOnPc will be called on the Computer instance and for c2 on MacbookPro instance.

4.4. A Super Method of a Particular Object

Suppose you have the following method in the Computer superclass:

public Double calculateValue(Double initialValue) {
    return initialValue/1.50;
}

and this one in MacbookPro subclass:

@Override
public Double calculateValue(Double initialValue){
    Function<Double, Double> function = super::calculateValue;
    Double pcValue = function.apply(initialValue);
    return pcValue + (initialValue/10) ;
}

A call to calculateValue method on a MacbookPro instance:

macbookPro.calculateValue(999.99);

will also produce also a call to calculateValue on the Computer superclass.

5. Constructor References

5.1. Create a New Instance

Referencing a constructor to instantiate an object can be quite simple:

@FunctionalInterface
public interface InterfaceComputer {
    Computer create();
}

InterfaceComputer c = Computer::new;
Computer computer = c.create();

What if you have two parameters in a constructor?

BiFunction<Integer, String, Computer> c4Function = Computer::new; 
Computer c4 = c4Function.apply(2013, "white");

If parameters are three or more you have to define a new Functional interface:

@FunctionalInterface 
interface TriFunction<A, B, C, R> { 
    R apply(A a, B b, C c); 
    default <V> TriFunction<A, B, C, V> andThen( Function<? super R, ? extends V> after) { 
        Objects.requireNonNull(after); 
        return (A a, B b, C c) -> after.apply(apply(a, b, c)); 
    } 
}

Then, initialize your object:

TriFunction <Integer, String, Integer, Computer> c6Function = Computer::new;
Computer c3 = c6Function.apply(2008, "black", 90);

5.2. Create an Array

Finally, let's see how to create an array of Computer objects with five elements:

Function <Integer, Computer[]> computerCreator = Computer[]::new;
Computer[] computerArray = computerCreator.apply(5);

6. Conclusion

As we're starting to see, the double colon operator – introduced in Java 8 – will be very useful in some scenarios, and especially in conjunction with Streams.

It's also quite important to have a look at functional interfaces for a better understanding of what happens behind the scenes.

The complete source code for the example is available in this GitHub project – this is a Maven and Eclipse project so that it can be imported and used as-is.