If you're reading this, that probably means you've got another window or tab open with some Java code that uses a double-colon notation that looks something like this:

Integer::compareTo

And you're asking yourself: "What is this thing?"

Or maybe you're trying to use that double-colon notation in your own code and getting compilation errors. You're here because you're wondering why.

In either case, I'll answer your question. Because I'm cool like that.

It's Called a Method Reference

That notation that you're looking at is called a method reference. It's a special type of lambda expression that you can include in your Java code.

Why would you want to include it? To tighten your code up a bit and make it easier to read. It's really no more complicated than that.

Just keep in mind that it's a substitute for a lambda expression. So you can't use it just anywhere. You can only use it where you use lambda expressions.

That, by the way, might be the reason that you're seeing a compilation error. You're trying to use a method reference in a place that doesn't require a functional interface. The compiler will complain about that every time.

Let me show you where you can use it.

Look for a Function Parameter

Whenever you see a Function as the sole parameter (or one of the parameters) in a method signature, that's a dead giveaway that you can use a method reference there.

Consider the map() method from the Java Stream API as an example. The signature looks like this:

map(Function<? super T,? extends R> mapper)

Let's leave alone the complexities associated with the type parameters for the moment. Just focus on the single type inside the parentheses. It's a Function.

And that means you can use a method reference there.

Let me show you an example using the following model class:

public class Employee {

    private String id;    
    private String lastName;
    private String firstName;

//getters and setters

}

Create some test data with this code:

List<Employee> employees = new ArrayList<>();

Employee employee1 = new Employee();
employee1.setId("A17");
employee1.setFirstName("Frank");
employee1.setLastName("Smith");
employees.add(employee1);

Employee employee2 = new Employee();
employee2.setId("B12");
employee2.setFirstName("Bunny");
employee2.setLastName("Cheshire");
employees.add(employee2);

Employee employee3 = new Employee();
employee3.setId("C11");
employee3.setFirstName("Glenn");
employee3.setLastName("Kin");
employees.add(employee3);

Now with all of that in place, take a look at this use of the Stream API:

List<String> lastNames = employees
                            .stream()
                            .map(Employee::getLastName)
                            .collect(Collectors.toList());

System.err.println(lastNames);

That's going to give you the following output:

[Smith, Cheshire, Kin]

So let me explain what happened.

Hone in on the method reference. That's the Employee::getLastName bit in the map() invocation.

For starters, note that you'll use method references with the class name. Not the object name.

You might be tempted to try the above code with employee::getLastName. But that won't work.

Next, you identify the method name after the double-colon but you don't include parentheses. That's important, too.

So this won't work: Employee::getLastName().

Just leave it exactly as you see it above.

What's going on inside that map() method is it's mapping the entire Employee object to just the employee's last name.

That method reference, by the way, is equivalent to this code:

List<String> lastNames = employees
                            .stream()
                            .map(employee -> employee.getLastName())
                            .collect(Collectors.toList());

That map() invocation uses the "traditional" type of lambda expression. It will produce the same output as the example with the method reference.

Look for a Consumer Parameter

If you're familiar with the forEach() method, then you probably know it accepts a Consumer as its sole parameter.

And guess what? That means you can use a method reference there as well.

That's because Consumer, like Function, is a functional interface.

The difference is that Consumer doesn't return anything whereas Function does.

So now take that List of last names you got from the previous code block and do this with it:

lastNames.forEach(System.out::println);

That's going to give you this output:

Smith
Cheshire
Kin

Note that they're on different lines. That's because the code calls the System.out.println() method for each and every object in the list. And that method, by definition, puts everything on its own line.

And once again note the use of double colons. This time it's after System.out. That's going to return the standard print stream. From there, the code just invokes println with no parentheses again.

Static Methods

By the way: that last example brings up a great point. You can use method references with static methods. You don't have to use them only on instances.

System.out, for example, is a static reference. And yet the code above works beautifully.

So don't think that you're required to create an instance of anything if you want to use method references. 

Any Functional Interface

You can use method references with any functional interface. Feel free to use them as you see fit.

But again: you can't use them in a method that doesn't accept a functional interface as one of its parameters. That's a violation.

Feel free to experiment with different functional interfaces and use different types of method references until you get the "feel" of how to work with them.

Yes, Constructors Too

You can create an empty object with a method reference as well. The code for that looks something like this:

Employee::new

You'll need to do that on an object that accepts an empty constructor or the compiler will complain. But it works just fine otherwise.

You'll find that you often want to go that route when you're required to use a Supplier with a method you're calling.

Wrapping It Up

Now you know a little bit more about method references in the Java programming language.

Play around a bit. Use different methods with different functional interfaces to fully understand how you can write more concise, easier-to-read code.

Have fun!

Photo by cottonbro from Pexels