Method and Constructor References in Java

When a lambda does nothing but call an existing method, a method reference says the same thing with less noise.

Definition

A method reference is shorthand for a lambda that only calls one existing method. It uses the :: operator and carries exactly the same meaning.

names.forEach(name -> System.out.println(name));   // lambda
names.forEach(System.out::println);                // method reference

The four forms

FormSyntaxEquivalent lambda
Static methodType::staticMethodargs -> Type.staticMethod(args)
Instance method of a particular objectobject::instanceMethodargs -> object.instanceMethod(args)
Instance method of an arbitrary objectType::instanceMethod(obj, rest) -> obj.instanceMethod(rest)
ConstructorType::newargs -> new Type(args)

1. Static method

Function<String, Integer> parse = Integer::parseInt;
BinaryOperator<Integer> max = Integer::max;

System.out.println(parse.apply("250"));    // 250

2. Instance method of a particular object

Logger logger = Logger.getLogger("app");
Consumer<String> log = logger::info;        // this logger, every time

String prefix = "note-";
Predicate<String> startsWithPrefix = prefix::startsWith;

3. Instance method of an arbitrary object

Function<String, Integer> length = String::length;
// equivalent to  text -> text.length()

Comparator<String> natural = String::compareTo;
// equivalent to  (a, b) -> a.compareTo(b)

This is the form that confuses people. The first parameter becomes the receiver, and the remaining parameters become the arguments.

4. Constructor

Supplier<List<String>> newList = ArrayList::new;
Function<String, StringBuilder> newBuilder = StringBuilder::new;
BiFunction<String, Integer, Note> newNote = Note::new;

IntFunction<String[]> newArray = String[]::new;    // array constructor
String[] array = names.stream().toArray(String[]::new);

Static compared with arbitrary instance

// Both look like Type::method, but the shapes differ
Function<String, Integer> viaStatic   = Integer::parseInt;   // Integer.parseInt(s)
Function<String, Integer> viaInstance = String::length;      // s.length()

The compiler works out which is meant from the signature of the functional interface. Ambiguity is only possible when a class has both a static and an instance method with the same name and compatible shapes, which is rare and produces a clear error.

In practice

List<Note> notes = repository.findAll();

List<String> titles = notes.stream()
        .map(Note::title)                     // an accessor
        .filter(Objects::nonNull)             // a static utility
        .map(String::strip)                   // arbitrary instance
        .sorted(String::compareToIgnoreCase)
        .toList();

Map<Boolean, List<Note>> split = notes.stream()
        .collect(Collectors.partitioningBy(Note::isPublished));

notes.forEach(System.out::println);

Read side by side with the lambda equivalents, the reference version is noticeably less cluttered because the parameter name adds nothing.

When a lambda is better

// A method reference cannot express extra work
.map(note -> note.title().toUpperCase())

// Nor can it reorder or ignore parameters
.reduce((a, b) -> b)

// Nor add a condition
.filter(note -> note.views() > 100)

Use a reference only when the lambda body is exactly one call with the parameters in the same order. Otherwise a lambda is clearer.

References to your own methods

public class NoteService {

    public List<String> summaries(List<Note> notes) {
        return notes.stream()
                .map(this::summarise)        // an instance method of this object
                .toList();
    }

    private String summarise(Note note) {
        return note.title() + " (" + note.views() + " views)";
    }

    public static String slug(Note note) {
        return note.title().toLowerCase().replace(" ", "-");
    }
}
List<String> slugs = notes.stream().map(NoteService::slug).toList();
Extracting a long lambda into a private method and referring to it with this::name is the standard way to keep a stream pipeline readable. The method also becomes independently testable.

A note on evaluation

Note note = notes.get(0);
Supplier<String> title = note::title;    // note is evaluated NOW
note = notes.get(1);                     // the supplier still uses the first note

For the bound instance form, the receiver expression is evaluated when the reference is created, not when it is invoked.

Common mistakes

  • Writing String::length() with brackets. A method reference never has an argument list.
  • Trying to use a reference where extra work is needed inside the body.
  • Confusing Type::instanceMethod with object::instanceMethod.
  • Expecting this::method to pick up a later reassignment of the receiver.
  • Using a reference that makes the code shorter but less obvious.

Best practices

  • Use a method reference when the lambda is a pure delegation.
  • Extract complex lambdas into named private methods and reference those.
  • Prefer Objects::nonNull, String::strip and similar standard references, which read well.
  • Keep a lambda when the reference would obscure what happens.

Practice

  1. Convert five lambdas of your own into method references, and identify which cannot be converted.
  2. Explain why String::length works as a Function<String, Integer> even though length takes no argument.
  3. Write a constructor reference that builds a Note from a title and a view count.
  4. Show a case where Type::method and object::method would both compile and mean different things.
  5. Refactor a five line lambda inside a stream into a private method plus a reference.

Conclusion

A method reference is a lambda with the ceremony removed. Use it when the body is exactly one call, keep a lambda when anything else happens, and extract to a named method when the logic deserves a name.

Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All Java notes →
Java

Introduction to Java

Java is a statically typed, object oriented language that compiles to bytecode and runs on a virtual machine, which is what makes it portable.

Read more

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.