Using method references in Java 8

Method references in Java 8 allows us to convert a call to a method or a constructor into instance of functional interface. For simple lambda expressions like x -> x.foo() using method references results in expression FooClass::foo that is more clear and readable (if this is your first encounter with method references you may feel that they are more complicated than lambdas - don’t worry most people go through this stage, after a while you get used to them and you will start to appreciate their beauty).

We will start by demonstrating how to use method references to call instance methods. A lambda expression of form (ObjClass obj) -> obj.method() is equivalent to method reference ObjClass::method, for example:

class Person {
	private final String name;
	public Person(String name) {
		 this.name = name;
	}
	public String getName() {
		 return name;
	}
}

// Both functions do the same thing:
Function<Person, String> getNameLambda = person -> person.getName();
Function<Person, String> getNameMethRef = Person::getName;

// How to use it with stream:
Stream.of(new Person("Mike"), new Person("Maya"), new Person("Carl"))
	.map(person -> person.getName())
	.collect(toList());

Stream.of(new Person("Mike"), new Person("Maya"), new Person("Carl"))
	.map(Person::getName)
	.collect(toList());

Another kind of lambda expressions that can be changed to method references has form arg -> someObj.method(arg), here the instance on which method is called is constant but the argument changes. Let’s continue our example with Persons:

List<Person> linuxUsers = new ArrayList<>();

Consumer<Person> addUserLambda = person -> linuxUsers.add(person);
Consumer<Person> addUserMethRef = linuxUsers::add;

Stream.of(new Person("Mike"), new Person("Maya"), new Person("Carl"))
	.forEach(person -> linuxUsers.add(person));

Stream.of(new Person("Mike"), new Person("Maya"), new Person("Carl"))
	.forEach(linuxUsers::add)

This aproach also works with methods that have more than one parameter:

class Person {
	private String name;
	public void setName(String firstName, String lastName) {
		 name = firstName + " " + lastName;
	}
}
Person anonymous = new Person();

BiConsumer<String, String> setNameMethRef = 
	(first, last) -> anonymous.setName(first, last);

BiConsumer<String, String> setNameMethRef = anonymous::setName;

When we provide both instance on which to call a method and all method arguments we may use method references too:

@FunctionalInterface
public interface TriConsumer<T1,T2,T3> {
    void apply(T1 arg1, T2 arg2, T3 arg3);
}

TriConsumer<Person, String, String> setNameOnPersonL = 
	(person, first, last) -> person.setName(first, last);

TriConsumer<Person, String, String> setNameOnPersonMR =
	Person::setName;

Using method references when working with static methods is also easy, for example:

IntBinaryOperator maxLambda = (a, b) -> Integer.max(a, b);
IntBinaryOperator maxMethRef = Integer::max;

DoubleSupplier randomLambda = () -> Math.random();
DoubleSupplier randomMethRef = Math::random;

Now let’s move to constructor references. Every lambda expression in form () -> new FooClass() can be rewritten to use constructor reference as FooClass::new, for example:

Supplier<ArrayList<Person>> createListLambda = () -> new ArrayList<>();
Supplier<ArrayList<Person>> createListMethRef = ArrayList::new;

// This also work when constructor has parameters:
class Person {
	public Person(String name) {
		 this.name = name;
	}
	...
}

Function<String, Person> createPersonWithNameL =
	name -> new Person(name);

Function<String, Person> createPersonWithNameMR =
	Person::new;

We may even use constructor references to create arrays:

IntFunction<String[]> createArrayL = size -> new String[size];
IntFunction<String[]> createArrayMR = String[]::new;

// Unfortunately this doesn't work with 2D arrays
BiFunction<Integer, Integer, String[][]> createArray2DL = 
	 (rows, cols) -> new String[rows][cols]

// This will create SINGLE ARRAY of arrays not 2D array
// Each row of this array will be initially set to null
IntFunction<Integer, String[][]> notWork = String[][]::new;

The last example that I want to present illustrates how to use method references to refer to super class methods:

public abstract class SuperClass {
    void method() { 
        System.out.println("superclass method()");
    }
}

public class SubClass extends SuperClass {
    @Override
    void method() {
        Runnable superMethodL = () -> super.method();
        Runnable superMethodMR = SubClass.super::method;
    }
}

So after all this examples it’s your turn now, launch your favorite IDE and write some Java code with method references!

marcin-chwedczuk

A Programmer, A Geek, A Human