Method References in Depth

Streams · lesson 26 of 42 · 4 min read

The four shapes of a method reference, what each one expands to, and when a lambda is clearer.

Open this lesson in the learning hub

Key points

  • Static: Main::tag becomes s -> Main.tag(s). The stream element is the argument.
  • Unbound instance: String::length becomes s -> s.length(). The element is the receiver.
  • Bound instance: prefix::concat becomes s -> prefix.concat(s). The receiver is captured once, right there.
  • Constructor: User::new becomes s -> new User(s), and String[]::new becomes an array factory.
  • A method reference is shorter, never faster. Use a lambda the moment arguments need reordering or extra work.
  • Watch the bound form: it captures the receiver when the reference is created, not when the stream runs.

Example

import java.util.*;
import java.util.function.*;
import java.util.stream.*;

public class Main {
    record User(String name) {
        String shout() {
            return name.toUpperCase();
        }
    }

    static String tag(String s) {
        return "#" + s;
    }

    public static void main(String[] args) {
        List<String> names = List.of("ada", "grace");

        // 1. Static:   Main::tag      is   s -> Main.tag(s)
        System.out.println("static   : " + names.stream().map(Main::tag).toList());

        // 2. Unbound:  String::length is   s -> s.length()
        System.out.println("unbound  : " + names.stream().map(String::length).toList());

        // 3. Bound:    prefix::concat is   s -> prefix.concat(s)
        String prefix = ">> ";
        System.out.println("bound    : " + names.stream().map(prefix::concat).toList());

        // 4. Constructor: User::new  is   s -> new User(s)
        List<User> users = names.stream().map(User::new).toList();
        System.out.println("ctor     : " + users.stream().map(User::shout).toList());

        // The receiver of a bound reference is captured once, when the reference is created.
        BinaryOperator<Integer> ref = Integer::sum;
        System.out.println("sum ref  : " + ref.apply(2, 3));

        // Reach for a lambda when the arguments need reordering or extra work.
        System.out.println("no ref   : " + names.stream().map(n -> n.substring(0, 1) + ".").toList());
        System.out.println("sorted   : " + Stream.of("pear", "fig")
                .sorted(Comparator.comparing(String::length)).toList());
    }
}

Four shapes, one rule: work out what the element becomes, the receiver or the argument.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Streams course, and every lesson in it is listed on the Streams contents page.