Modern Java

45. Method References

Method references (::) are shorthand for lambdas that simply call a single existing method โ€” four kinds: static, bound instance, unbound instance, and constructor references.

Java 8+ (method references); Java 21 (core language)

Explanation

Method references are syntactic sugar for a lambda that calls one method. They are more readable when the lambda does nothing but forward arguments to an existing method.

Four kinds: - Static: ClassName::staticMethod โ€” e.g., Integer::parseInt, replaces s -> Integer.parseInt(s). - Bound instance: instance::method โ€” e.g., "prefix"::concat, replaces s -> "prefix".concat(s); the instance is captured. - Unbound instance: ClassName::instanceMethod โ€” e.g., String::toUpperCase, replaces s -> s.toUpperCase(); the instance is the first argument. - Constructor: ClassName::new โ€” e.g., ArrayList::new, replaces () -> new ArrayList<>().

Prefer a method reference whenever the lambda does nothing beyond calling a method โ€” it reduces noise. Use a lambda when there's any additional logic or argument reordering.

Example

import java.util.Arrays;
import java.util.List;
import java.util.function.*;
import java.util.stream.Collectors;

public class MethodReferences {
    static int doubleIt(int x) { return x * 2; }
    record Person(String name, int age) {}

    public static void main(String[] args) {
        // 1. Static method reference
        Function<String, Integer> parse = Integer::parseInt; // s -> Integer.parseInt(s)
        System.out.println(parse.apply("42")); // 42

        List<Integer> nums = List.of(1, 2, 3, 4, 5);
        List<Integer> doubled = nums.stream()
            .map(MethodReferences::doubleIt) // static ref
            .toList();
        System.out.println(doubled);

        // 2. Bound instance method reference
        String prefix = "Hello, ";
        Function<String, String> greet = prefix::concat; // s -> prefix.concat(s)
        System.out.println(greet.apply("Alice")); // Hello, Alice

        // 3. Unbound instance method reference
        Function<String, String> upper = String::toUpperCase; // s -> s.toUpperCase()
        List<String> names = List.of("alice", "bob", "charlie");
        names.stream().map(String::toUpperCase).forEach(System.out::println);

        // 4. Constructor reference
        Function<String, StringBuilder> sbFactory = StringBuilder::new;
        StringBuilder sb = sbFactory.apply("start");
        System.out.println(sb);

        // Comparator via method reference
        var people = List.of(new Person("Charlie", 30), new Person("Alice", 25), new Person("Bob", 35));
        people.stream()
              .sorted(Comparator.comparing(Person::name)) // unbound ref
              .forEach(p -> System.out.println(p.name() + ":" + p.age()));

        // println as a consumer
        List.of("x", "y", "z").forEach(System.out::println); // bound: System.out::println
    }
}

Sources