Method References
Reading Time: 7 Minutes
Difficulty: Beginner
Topic Summary
A method reference is a shorthand for a lambda expression that simply calls an existing method. Instead of writing x -> SomeClass.someMethod(x), you write SomeClass::someMethod. The double-colon :: operator is the method reference operator. It makes code even more concise and readable when the lambda body is just a method call.
What You'll Learn
- What method references are and when to use them
- The 4 types of method references with examples
- How method references relate to lambda expressions
- How constructor references work
Prerequisites
- Lambda Expressions (Lesson 1)
- Functional Interfaces (Lesson 2)
Explanation
What is a Method Reference?
A method reference lets you point to an existing method by name. It's basically a lambda that calls only one method. If your lambda looks like:
x -> SomeClass.someMethod(x)
You can replace it with:
SomeClass::someMethod
Both do the same thing, but the method reference is cleaner and more descriptive.
The 4 Types of Method References
| Type | Syntax | Lambda Equivalent |
|---|---|---|
| Static method | ClassName::staticMethod | x -> ClassName.staticMethod(x) |
| Instance method of a specific object | object::instanceMethod | x -> object.instanceMethod(x) |
| Instance method of an arbitrary object | ClassName::instanceMethod | (obj, args) -> obj.instanceMethod(args) |
| Constructor reference | ClassName::new | args -> new ClassName(args) |
Type 1: Static Method Reference
When your lambda calls a static method:
// Lambda version
Function<String, Integer> parse = s -> Integer.parseInt(s);
// Method reference version
Function<String, Integer> parse = Integer::parseInt;
System.out.println(parse.apply("42")); // 42
More examples:
// Math.abs
Function<Integer, Integer> abs = Math::abs;
// String.valueOf
Function<Integer, String> intToStr = String::valueOf;
// System.out.println — that's a static reference!
Consumer<String> printer = System.out::println;
Type 2: Instance Method of a Specific Object
When your lambda calls an instance method on a specific, known object:
String prefix = "Hello, ";
// Lambda version
Function<String, String> greeter = name -> prefix.concat(name);
// Method reference version
Function<String, String> greeter = prefix::concat;
System.out.println(greeter.apply("Alice")); // Hello, Alice
Another example:
List<String> names = new ArrayList<>();
// Lambda
Consumer<String> adder = name -> names.add(name);
// Method reference
Consumer<String> adder = names::add;
Type 3: Instance Method of an Arbitrary Object
When your lambda calls an instance method on the parameter itself (not a specific object, but whichever object is passed in):
// Lambda version
Function<String, String> upper = s -> s.toUpperCase();
// Method reference version
Function<String, String> upper = String::toUpperCase;
System.out.println(upper.apply("hello")); // HELLO
Here the method toUpperCase() belongs to String, and it's called on whatever String is passed in. More examples:
// Comparator using String.compareTo
Comparator<String> comp = String::compareTo;
// Predicate using String.isEmpty
Predicate<String> isEmpty = String::isEmpty;
Type 4: Constructor Reference
When your lambda creates a new object with new:
// Lambda version
Supplier<ArrayList<String>> listMaker = () -> new ArrayList<>();
// Constructor reference version
Supplier<ArrayList<String>> listMaker = ArrayList::new;
ArrayList<String> list = listMaker.get();
With arguments:
class Student {
String name;
int age;
Student(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() { return name + " (" + age + ")"; }
}
// BiFunction takes two args and produces an object
BiFunction<String, Integer, Student> creator = Student::new;
Student s = creator.apply("Alice", 20);
System.out.println(s); // Alice (20)
When to Use Method References vs Lambdas
Use a method reference when:
- The lambda body is just a single method call
- No modification of the argument(s) is needed
- Readability improves
Use a lambda when:
- You need to transform or combine arguments before passing
- The logic is more than a simple method call
- You want inline logic
// Use method reference — cleaner
list.stream().map(String::toUpperCase).forEach(System.out::println);
// Use lambda — you need custom logic
list.stream().map(s -> s.toUpperCase() + "!").forEach(System.out::println);
Real-World Analogy
Method references are like speed dial on a phone:
- Instead of typing the full number every time (lambda), you save it as contact "Mom" and just press "Mom" (method reference).
System.out::printlnis speed dial for "take this and print it to the screen."String::toUpperCaseis speed dial for "call toUpperCase on whatever String you give me."
Code Example
Example 1: All 4 Types
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public class MethodReferencesDemo {
// A static method to demonstrate Type 1
static boolean isLongWord(String s) {
return s.length() > 5;
}
public static void main(String[] args) {
List<String> words = Arrays.asList("Java", "Python", "Go", "Kotlin", "JavaScript");
// Type 1: Static method reference
System.out.println("=== Type 1: Static ===");
List<String> longWords = words.stream()
.filter(MethodReferencesDemo::isLongWord) // static method ref
.collect(Collectors.toList());
System.out.println(longWords); // [Python, Kotlin, JavaScript]
// Type 2: Instance method of a specific object
System.out.println("\n=== Type 2: Specific Instance ===");
String prefix = "Language: ";
Function<String, String> labelMaker = prefix::concat;
words.stream()
.map(labelMaker)
.forEach(System.out::println);
// Language: Java, Language: Python, ...
// Type 3: Instance method of arbitrary object (parameter itself)
System.out.println("\n=== Type 3: Arbitrary Instance ===");
words.stream()
.map(String::toUpperCase) // method ref on the parameter
.sorted(String::compareTo) // compareTo on arbitrary String
.forEach(System.out::println);
// GO, JAVA, JAVASCRIPT, KOTLIN, PYTHON
// Type 4: Constructor reference
System.out.println("\n=== Type 4: Constructor ===");
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// Convert list of Strings to list of StringBuilders
List<StringBuilder> builders = names.stream()
.map(StringBuilder::new) // constructor reference
.collect(Collectors.toList());
builders.forEach(sb -> System.out.println(sb.reverse())); // reverse each
// ecilA, boB, eilrahC
}
}
Output
=== Type 1: Static ===
[Python, Kotlin, JavaScript]
=== Type 2: Specific Instance ===
Language: Java
Language: Python
Language: Go
Language: Kotlin
Language: JavaScript
=== Type 3: Arbitrary Instance ===
GO
JAVA
JAVASCRIPT
KOTLIN
PYTHON
=== Type 4: Constructor ===
ecilA
boB
eilrahC
Example 2: Practical Usage with Sorting and Printing
import java.util.*;
public class MethodRefPractical {
public static void main(String[] args) {
List<String> students = Arrays.asList("Zara", "Alice", "Mike", "Bob");
// Sort using method reference
students.sort(String::compareTo);
// Print each using method reference
students.forEach(System.out::println);
System.out.println("---");
// Convert marks to pass/fail
List<Integer> marks = Arrays.asList(45, 82, 33, 91, 55);
marks.stream()
.map(MethodRefPractical::gradeFor) // static method ref
.forEach(System.out::println);
}
static String gradeFor(int mark) {
if (mark >= 80) return "A";
if (mark >= 60) return "B";
if (mark >= 40) return "C";
return "F";
}
}
Output
Alice
Bob
Mike
Zara
---
C
A
F
A
C
Common Mistakes
- ❌ Mistake: Writing
obj::method()with parentheses → ✅ Fix: Method references don't have parentheses:obj::methodnotobj::method() - ❌ Mistake: Using a method reference when the lambda modifies the argument → ✅ Fix: If you need to do
s -> s.trim().toUpperCase(), that's two steps — use a lambda, not a single method reference - ❌ Mistake: Confusing Type 2 and Type 3 — both use
ClassName::methodsometimes → ✅ Fix: Type 2 uses a specific instance variable (myObj::method); Type 3 uses the class name when the method is called on the input parameter itself (String::toUpperCase) - ❌ Mistake: Using constructor reference when constructor doesn't match the functional interface → ✅ Fix: The constructor's parameters must match the functional interface's method signature
Best Practices
- Use method references whenever a lambda body is just a single method call — it's cleaner
System.out::printlnis one of the most common and useful method references- Use constructor references (
ClassName::new) with streammap()to create objects from data - Don't force method references if they make the code harder to read — lambdas are fine too
- Combine method references with Stream API for clean, fluent data processing pipelines
Interview Questions
Q: What is a method reference in Java?
A: A method reference is a shorthand syntax for a lambda expression that calls an existing method. It uses the :: operator. For example, String::toUpperCase is equivalent to s -> s.toUpperCase().
Q: What are the 4 types of method references?
A: (1) Static: ClassName::staticMethod, (2) Instance of specific object: object::instanceMethod, (3) Instance of arbitrary object: ClassName::instanceMethod, (4) Constructor: ClassName::new.
Q: What is the difference between a method reference and a lambda?
A: Both implement functional interfaces. A method reference is simply a cleaner syntax for lambdas that only call a single method without modification. Lambdas are more flexible — they can contain any expression or block of code.
Q: Can you use method references for constructors?
A: Yes. Constructor references use the syntax ClassName::new. The functional interface's method signature must match the constructor's parameters. For example, ArrayList::new works as a Supplier<ArrayList>.
Quick Revision
✔ Method reference = shorthand for lambda that calls one method: Class::method
✔ Type 1 — Static: Integer::parseInt
✔ Type 2 — Specific instance: myObj::method
✔ Type 3 — Arbitrary instance (on parameter): String::toUpperCase
✔ Type 4 — Constructor: ArrayList::new
✔ Use when lambda body is just one method call — otherwise use lambda
Related Topics
- Lambda Expressions
- Functional Interfaces
- Stream API
- Constructor in Java
Next Lesson
Lesson 7 — Date and Time API