What are Method References?
Method references in Java 8 are simply shortcuts or cleaner ways to write lambda expressions when all you’re doing is calling a single existing method.
Think of them as a simpler, more readable way to pass methods as parameters.
Let’s understand with a simple real-life analogy:
Imagine you have a friend who always forwards your messages to someone else.
Instead of saying “take this message and forward it,” you could just say “forward it.”
Method references work in a similar way — they provide a shorter way to say “use this existing method.”
For example, instead of writing:
names.forEach(name -> System.out.println(name));
You can write it more concisely as:
names.forEach(System.out::println);
The ::
operator is the special syntax for method references.
It tells Java “I want to use this method here”.
On the surface, method references may seem similar to lambda expressions, but they offer a more elegant solution when working with functional interfaces.
By using method references, you can simplify your code and make it more readable.
To use method references effectively, you should understand that they work as shortcuts for invoking existing methods.
Types of Method References
Types of method references can be categorized into four main groups. Knowing these types will help you apply them correctly in your code:
- Reference to static methods (like
Math::max
) - Reference to instance methods of objects (like
myString::length
) - Reference to instance methods of any object of a specific type (like
String::toUpperCase
) - Reference to constructors (like
ArrayList::new
)
1. Reference To Static Methods
Static method references provide a way to refer to static methods without actually executing them.
The basic syntax for static method references is:
ClassName::staticMethodName
Static method references are particularly useful when you want to pass a static method as an argument to another method, especially when working with streams, collections, or functional interfaces.
Below is an example of using static method references in comparison with lambda expressions
// Traditional way using lambda List numbers = Arrays.asList("1", "2", "3"); numbers.stream() .map(str -> Integer.parseInt(str)) // Lambda expression .forEach(System.out::println); // Using static method reference numbers.stream() .map(Integer::parseInt) // Static method reference .forEach(System.out::println);
2. Reference to instance methods of objects
An instance method reference of a particular object refers to a method that belongs to a specific instance (object) of a class.
This type of method reference uses the syntax:
objectReference::instanceMethodName
where objectReference
is an existing object instance.
Below is an example of using instance method references in comparison with lambda expressions
public class StringProcessor { public String convertToUpperCase(String str) { return str.toUpperCase(); } } // Usage example public class Main { public static void main(String[] args) { // Creating an instance StringProcessor processor = new StringProcessor(); // Traditional lambda expression Function lambda = s -> processor.convertToUpperCase(s); // Equivalent instance method reference Function methodRef = processor::convertToUpperCase; // Using the method reference String result = methodRef.apply("hello"); // Returns "HELLO" } }
Method reference is bound to a specific object instance and the referenced method must exist in the object’s class.
3. Reference to instance methods of object of a specific type
Instance method references of an arbitrary object are one of the types of method references in Java 8+.
Below is its syntax:
ClassName::instanceMethodName
They allow you to refer to instance methods that belong to objects of a particular class.
Below is an example of using instance method references of other classes in comparison with lambda expressions
/* --- Single parameter --- */ // Traditional lambda expression Function upperLambda = str -> str.toUpperCase(); // Equivalent method reference Function upperMethod = String::toUpperCase; // Using the method reference String result = upperMethod.apply("hello"); // Returns "HELLO" /* --- With Multiple Parameters --- */ // Traditional lambda expression BiPredicate containsLambda = (s1, s2) -> s1.contains(s2); // Equivalent method reference BiPredicate containsMethod = String::contains; // Using the method reference boolean result = containsMethod.test("Hello World", "World");
The method being referenced must exist in the specified class and the parameter types must match the functional interface’s abstract method.
Also, the return type must be compatible with the functional interface’s abstract method.
4. Constructor References
Constructor references provide a way to reference class constructors.
This feature is part of the method reference concept and offers a more concise way to create objects, especially when working with functional interfaces.
Its syntax is:
ClassName::new
Below are the examples of constructor references
/* --- Basic Constructor Reference --- */ // Traditional way Supplier personSupplier = () -> new Person(); // Using constructor reference Supplier personSupplier = Person::new; /* --- Constructor with Parameters --- */ // Traditional way Function personFunction = name -> new Person(name); // Using constructor reference Function personFunction = Person::new; /* --- Array Constructor Reference --- */ // Creating an array using constructor reference Function arrayCreator = String[]::new; String[] array = arrayCreator.apply(5); // Creates String array of size 5
Method References vs Lambda Expressions
Method references and lambda expressions are two important features in modern Java for implementing functional interfaces.
While they often can be used interchangeably, understanding their differences and knowing when to use each is crucial for writing clean and maintainable code.
When to prefer method references over lambda expressions:
1. Direct Method Calls
When your lambda expression simply calls an existing method without any additional logic, method references are preferred.
// Instead of this list.stream().map(s -> s.toUpperCase()) // Use this list.stream().map(String::toUpperCase)
2. Constructor References
When creating new objects in a stream or functional interface:
// Instead of this list.stream().map(s -> new Student(s)) // Use this list.stream().map(Student::new)
3. Static Method Calls
When calling static methods
// Instead of this numbers.stream().map(n -> Math.abs(n)) // Use this numbers.stream().map(Math::abs)
Performance considerations
- Bytecode Generation
a. Both method references and lambda expressions are compiled to similar bytecode.
b. Neither approach has a significant performance advantage over the other.
c. The JVM can optimize both forms equally well
2. Memory Usage
a. Method references might have a slightly smaller memory footprint.
b. The difference is typically negligible in most applications
3. Runtime Performance
Both perform identically
Final Words
You can enhance your Java development experience by mastering method references, as they provide a more concise and readable alternative to lambda expressions in many scenarios.
They’re not just syntactic sugar — they represent a fundamental shift towards more functional programming patterns in Java.
When used correctly, they can reduce boilerplate code by up to 30% compared to traditional anonymous inner classes.