What you'll learn

  • What a method reference is and when to use one
  • The four kinds — static, bound, unbound and constructor
  • Translating between lambdas and method references
  • Building comparators from method references

When a lambda does nothing except call an existing method, you can replace it with a method reference using ::. It’s shorter, and often reads almost like English: map(Order::id) — “map each order to its id”.

skus.forEach(s -> System.out.println(s));   // lambda
skus.forEach(System.out::println);          // method reference — same thing

The four kinds

KindSyntaxEquivalent lambda
Static methodClassName::staticMethods -> MethodReferences.isValidSku(s)
Method of a particular objectobject::methods -> prefix.equals(s)
Method of an arbitrary object of a typeClassName::instanceMethods -> s.toUpperCase()
ConstructorClassName::newn -> new Customer(n)

1. Static method

Predicate<String> valid = MethodReferences::isValidSku;

2. Instance method of a particular (“bound”) object

String prefix = "SKU:";
Predicate<String> hasPrefix = prefix::equals;   // always calls prefix.equals(...)
skus.forEach(System.out::println);              // System.out is the object

3. Instance method of an arbitrary (“unbound”) object

Function<String, String> upper = String::toUpperCase;    // the argument becomes 'this'
Function<Customer, String> greet = Customer::greeting;
BiFunction<String, String, Boolean> same = String::equalsIgnoreCase;   // (a, b) -> a.equalsIgnoreCase(b)

This is the one that confuses people: String::toUpperCase takes a String and calls toUpperCase() on it. With two parameters, the first becomes the object and the second the argument.

4. Constructor

Function<String, Customer> create = Customer::new;
Supplier<List<String>> newList = ArrayList::new;
String[] array = list.stream().toArray(String[]::new);

When to use them

Use a method reference when it’s clearer than the lambda — usually when the method name says exactly what happens. Keep the lambda when you need to do anything extra (o -> o.amount() * 1.2), or when the reference would be ambiguous between overloaded methods.

Comparators read beautifully

customers.sort(Comparator.comparing(Customer::name));
orders.sort(Comparator.comparing(Order::customer)
                      .thenComparing(Order::amount)
                      .reversed());

The full example

// MethodReferences.java — the four kinds of method reference.
import java.util.*;
import java.util.function.*;

public class MethodReferences {

    static boolean isValidSku(String s) {
        return s.matches("[A-Z]{2}-\\d{3}");
    }

    record Customer(String name) {
        String greeting() { return "Hello, " + name; }
    }

    public static void main(String[] args) {
        List<String> skus = List.of("AX-200", "bad", "BX-310");

        // 1. Static method:            ClassName::staticMethod
        Predicate<String> valid = MethodReferences::isValidSku;          // s -> isValidSku(s)
        System.out.println(skus.stream().filter(valid).toList());       // [AX-200, BX-310]

        // 2. Instance method of a particular object:  object::method
        String prefix = "SKU:";
        Predicate<String> hasPrefix = prefix::equals;                     // s -> prefix.equals(s)
        System.out.println(hasPrefix.test("SKU:"));                      // true
        skus.forEach(System.out::println);                               // s -> System.out.println(s)

        // 3. Instance method of an arbitrary object of a type:  ClassName::instanceMethod
        Function<String, String> upper = String::toUpperCase;            // s -> s.toUpperCase()
        Function<Customer, String> greet = Customer::greeting;           // c -> c.greeting()
        System.out.println(upper.apply("acme") + " / " + greet.apply(new Customer("Globex")));

        // 4. Constructor:              ClassName::new
        Function<String, Customer> create = Customer::new;               // n -> new Customer(n)
        Supplier<List<String>> newList = ArrayList::new;                 // () -> new ArrayList<>()
        List<String> names = newList.get();
        names.add(create.apply("Initech").name());
        System.out.println(names);                                       // [Initech]

        // Comparator built from method references
        List<Customer> customers = new ArrayList<>(List.of(new Customer("Zed"), new Customer("Amy")));
        customers.sort(Comparator.comparing(Customer::name));
        System.out.println(customers);
    }
}

Try it yourself

Rewrite each lambda as a method reference:

Function<String, Integer> a = s -> Integer.parseInt(s);
Consumer<Object> b = o -> System.out.println(o);
Function<String, Integer> c = s -> s.length();
Supplier<Set<String>> d = () -> new HashSet<>();
Show a solution
Function<String, Integer> a = Integer::parseInt;     // static
Consumer<Object> b = System.out::println;             // bound
Function<String, Integer> c = String::length;         // unbound
Supplier<Set<String>> d = HashSet::new;               // constructor

Key takeaways

  • :: turns an existing method or constructor into a function.
  • Four kinds: static, bound (obj::m), unbound (Type::m) and constructor (Type::new).
  • With unbound references, the first argument becomes the object the method is called on.
  • Prefer them when they read more clearly than the lambda.

Example files