⚡IntermediateFunctional Programming

Method References

Replace simple lambdas with the compact :: method reference syntax to improve readability.

Overview

A method reference is a shorthand for a lambda that only calls a single method. Instead of s -> s.toUpperCase() you write String::toUpperCase. The :: syntax is called a method reference and works wherever a compatible functional interface is expected. There are four kinds: static method references, instance method references on a specific instance, instance method references on an arbitrary instance of a type, and constructor references.

Key Points

  • •Static method: ClassName::staticMethod (Integer::parseInt)
  • •Bound instance method: instance::method (System.out::println)
  • •Unbound instance method: ClassName::instanceMethod (String::toUpperCase)
  • •Constructor reference: ClassName::new (ArrayList::new)
  • •Method references are only possible when the lambda does nothing other than call the method
  • •The compiler infers which overload to use from the functional interface signature
  • •Method references are interchangeable with equivalent lambdas; choose whichever is clearer

Examples

Static method references

import java.util.List;
import java.util.function.Function;

public class Main {
    static int doubleIt(int n) { return n * 2; }
    static String describe(int n) { return "Number " + n; }

    public static void main(String[] args) {
        // Static method reference
        List<String> strings = List.of("1", "2", "3", "4");

        // Lambda: s -> Integer.parseInt(s)
        // Method reference: Integer::parseInt
        strings.stream()
               .map(Integer::parseInt)   // same as s -> Integer.parseInt(s)
               .map(Main::doubleIt)      // same as n -> doubleIt(n)
               .map(Main::describe)
               .forEach(System.out::println);
    }
}

Integer::parseInt replaces s -> Integer.parseInt(s). The compiler matches the lambda signature to the method signature automatically.

Bound and unbound instance method references

import java.util.List;
import java.util.function.Function;
import java.util.function.Consumer;

public class Main {
    public static void main(String[] args) {
        // Bound: specific instance
        // () -> System.out.println(...) becomes:
        Consumer<String> printer = System.out::println; // System.out is the specific instance
        printer.accept("hello"); // hello

        // Unbound: applies to any instance of String
        // s -> s.toUpperCase() becomes:
        Function<String, String> upper = String::toUpperCase;
        System.out.println(upper.apply("world")); // WORLD

        // Used in stream
        List<String> words = List.of("hello", "world", "java");
        words.stream()
             .map(String::toUpperCase)
             .forEach(System.out::println);
    }
}

Bound: the object is known (System.out). Unbound: the object comes from the first argument of the lambda (s in s -> s.toUpperCase()).

Constructor references

import java.util.List;
import java.util.ArrayList;
import java.util.function.Supplier;
import java.util.function.Function;
import java.util.stream.Collectors;

public class Main {
    static class User {
        String name;
        User(String name) { this.name = name; }
        public String toString() { return "User(" + name + ")"; }
    }

    public static void main(String[] args) {
        // Supplier: () -> new ArrayList<String>()
        Supplier<List<String>> listMaker = ArrayList::new;
        List<String> list = listMaker.get();
        list.add("item");
        System.out.println(list);

        // Function: name -> new User(name)
        Function<String, User> userFactory = User::new;
        User alice = userFactory.apply("Alice");
        System.out.println(alice); // User(Alice)

        // Collect to list using constructor reference in stream
        List<User> users = List.of("Bob", "Carol", "Dave")
                               .stream()
                               .map(User::new)
                               .collect(Collectors.toList());
        System.out.println(users);
    }
}

ClassName::new is a constructor reference. The compiler picks the matching constructor based on the functional interface signature.

Common Mistakes

  • Trying to use a method reference when the lambda body does more than call a single method. x -> x.trim().toUpperCase() cannot be simplified to a single method reference
  • Confusing bound and unbound references: String::toUpperCase is unbound (takes a String argument); instance::toUpperCase would be bound
  • Using the wrong number of arguments: if the functional interface has two parameters, the method must accept two parameters or take one receiver and one argument

Note

Apply the rule: if you can name the method and the lambda body does nothing other than call it, use a method reference. Prefer readability: if the method reference is obvious, use it; if it is cryptic, use a lambda.

Exercises

Write Java code and click Run to execute it instantly. When your output looks correct, mark as done.

Loading editor…

Completed: 0 / 4 exercises

Knowledge Check

Question 1 of 4

Which of the following is an unbound instance method reference?