Definition
- An unbound instance-method reference
Type::methodrefers to a method without a specific instance. - The lambda parameter is supplied as the receiver at call time.
Employee::getNamesatisfiesFunctionjust likee -> e.getName().- Both produce identical bytecode; the reference is syntactic sugar.
- Only applies when the lambda body is a single method call on its own parameter.
Instead of writing a sticky note saying 'take the employee and ask for the name', Priya at the National Bank of India just points at the 'getName' skill directly. The pointing is the method reference.
Understanding the Concepts
What is an unbound instance-method reference?
Employee::getName refers to the getName method without binding it to a specific object. The instance is supplied later as the first argument when the functional interface is invoked.
package com.bank.hr;
import java.util.*;
import java.util.stream.Collectors;
import java.util.function.Function;
public class MethodRefDemo {
public static void main(String[] args) {
List<Employee> employees = List.of(
new Employee("Rajesh", "Loans"),
new Employee("Priya", "IT"),
new Employee("Amit", "Finance")
);
// Lambda
List<String> namesLambda = employees.stream()
.map(e -> e.getName())
.collect(Collectors.toList());
// Method reference (equivalent)
List<String> namesRef = employees.stream()
.map(Employee::getName)
.collect(Collectors.toList());
System.out.println(namesRef);
}
}
[Rajesh, Priya, Amit]
How does the compiler match the reference?
The compiler matches Employee::getName to the target functional interface. If map expects a Function
| Lambda | Method reference | Target interface | |---|---|---| | e -> e.getName() | Employee::getName | Function<Employee, String> | | e -> e.getSalary() | Employee::getSalary | Function<Employee, BigDecimal> | | e -> e.toString() | Employee::toString | Function<Employee, String> |
When should you keep the lambda?
Method references only work for pure delegation. If you need any extra logic, keep the lambda.
// Method reference: pure delegation
.map(Employee::getName)
// Lambda required: null check
.map(e -> e.getName() != null ? e.getName() : "UNKNOWN")
// Lambda required: transformation
.map(e -> e.getName().toUpperCase())
// Lambda required: multiple fields
.map(e -> e.getName() + " (" + e.getDepartment() + ")")
What are the four types of method references?
| Type | Syntax | Example |
|---|---|---|
| Static method | Type::staticMethod | Integer::parseInt |
| Unbound instance | Type::instanceMethod | Employee::getName |
| Bound instance | instance::method | System.out::println |
| Constructor | Type::new | Employee::new |
What are common mistakes?
Mistake: using a method reference when extra logic is needed.
// WRONG: cannot add null check in a method reference
.map(Employee::getName) // NPE if employee is null
// CORRECT: use lambda for null safety
.filter(Objects::nonNull)
.map(Employee::getName)
What should a senior engineer know?
- Method references improve readability by naming the operation rather than describing it.
- They compile to the same bytecode as lambdas; there is no performance difference.
- Choosing between them is a clarity decision, not a style rule.
- Overusing method references where logic is involved actually hurts readability.
What are common interview follow-up questions?
- "What is the difference between Employee::getName and someEmployee::getName?" The first is unbound (parameter is receiver); the second is bound (specific object is the receiver).
- "Can a method reference throw?" Yes, if the underlying method throws.
- "Can you use method references with overloaded methods?" The compiler picks the overload matching the target functional interface.
Visual Overview
Lambda form: Method reference form:
e -> e.getName() Employee::getName
│ │
│ Compiler sees: │ Compiler sees:
│ parameter e, │ unbound method getName
│ calls getName on e │ on type Employee
▼ ▼
Function<Employee, String> Function<Employee, String>
│ │
└──── identical bytecode ─┘
Key Takeaway
When a lambda does nothing but call a single method on its parameter, rewrite it as an unbound instance-method reference. The lambda e -> e.getName() becomes Employee::getName, where the lambda's parameter becomes the method's receiver. Both satisfy the same functional interface and compile to identical bytecode.
At the National Bank of India, the codebase uses method references wherever a stream maps employees to their names, departments, or salaries, because the reference names the operation directly and is easier to read at a glance.
The key rule is that method references only apply to pure delegation: one method call on the parameter, no extra logic, no null checks. The moment you need to transform, combine, or guard, keep the explicit lambda.
Remember one sentence: Employee::getName replaces e -> e.getName() when the lambda is pure delegation, and the parameter becomes the receiver.
---