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
- The four forms
- 1. Static method
- 2. Instance method of a particular object
- 3. Instance method of an arbitrary object
- 4. Constructor
- Static compared with arbitrary instance
- In practice
- When a lambda is better
- References to your own methods
- A note on evaluation
- Common mistakes
- Best practices
- Practice
- Conclusion
-
Java Basics
- Introduction to Java
- Setting Up Java and Writing Your First Program
- Variables, Data Types and Literals in Java
- Type Casting and Type Conversion in Java
- Operators and Expressions in Java
- Input and Output in Java
- Comments, Keywords and Naming Conventions in Java
- Control Flow in Java: if, else and switch
- Loops in Java: for, while and do-while
- Methods
- Arrays and Strings
-
OOP
- Classes and Objects in Java
- Constructors in Java
- The this Keyword in Java
- The static Keyword in Java
- Encapsulation in Java
- Access Modifiers in Java
- Inheritance in Java
- Method Overriding and super in Java
- Polymorphism in Java
- Abstraction, Abstract Classes and Interfaces in Java
- Composition, Aggregation and Association in Java
- The Object Lifecycle in Java
- Core Java
- Exception Handling
-
Collections
- The Java Collections Framework
- List in Java: ArrayList, LinkedList, Vector and Stack
- Set in Java: HashSet, LinkedHashSet and TreeSet
- Map in Java: HashMap, LinkedHashMap and TreeMap
- How HashMap Works Internally in Java
- Queue and Deque in Java: ArrayDeque and PriorityQueue
- Iterators in Java
- Comparable and Comparator in Java
- Collections Utilities and Choosing the Right Collection
- Generics
- Java 8+
- Stream API
- Date and Time
- File and I/O
-
Multithreading
- Threads in Java: Processes, Runnable and Thread
- Thread Lifecycle in Java
- Synchronization in Java: synchronized and volatile
- Locks and Atomic Classes in Java
- Race Conditions and Deadlocks in Java
- The Executor Framework and Thread Pools in Java
- Future and CompletableFuture in Java
- Concurrent Collections in Java
- The Java Memory Model
- JVM and Memory
- Advanced Java
- Networking
- JDBC
- Testing
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 referenceThe four forms
| Form | Syntax | Equivalent lambda |
|---|---|---|
| Static method | Type::staticMethod | args -> Type.staticMethod(args) |
| Instance method of a particular object | object::instanceMethod | args -> object.instanceMethod(args) |
| Instance method of an arbitrary object | Type::instanceMethod | (obj, rest) -> obj.instanceMethod(rest) |
| Constructor | Type::new | args -> new Type(args) |
1. Static method
Function<String, Integer> parse = Integer::parseInt;
BinaryOperator<Integer> max = Integer::max;
System.out.println(parse.apply("250")); // 2502. 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 constructorString[] 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 noteFor 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::instanceMethodwithobject::instanceMethod. - Expecting
this::methodto 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::stripand similar standard references, which read well. - Keep a lambda when the reference would obscure what happens.
Practice
- Convert five lambdas of your own into method references, and identify which cannot be converted.
- Explain why
String::lengthworks as aFunction<String, Integer>even thoughlengthtakes no argument. - Write a constructor reference that builds a
Notefrom a title and a view count. - Show a case where
Type::methodandobject::methodwould both compile and mean different things. - 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.