Java 8 – Method References Explained with Examples
A method reference is a compact way to write a lambda that does nothing but call an existing method. Instead of writing x -> SomeClass.someMethod(x), you write SomeClass::someMethod. Method references make code shorter and more readable when the lambda body is just a single method call.
Four Types of Method References
| Type | Syntax | Lambda Equivalent |
|---|---|---|
| Static method | ClassName::staticMethod |
x -> ClassName.staticMethod(x) |
| Instance method of a specific object | instance::instanceMethod |
x -> instance.instanceMethod(x) |
| Instance method of an arbitrary object of a given type | ClassName::instanceMethod |
x -> x.instanceMethod() |
| Constructor | ClassName::new |
(args) -> new ClassName(args) |
Type 1 – Static Method Reference
import java.util.*;
import java.util.stream.*;
import java.util.function.*;
public class StaticMethodRef {
// A static method to use as a reference
public static int square(int n) {
return n * n;
}
public static boolean isEven(int n) {
return n % 2 == 0;
}
public static void main(String[] args) {
// Lambda version
Function<Integer, Integer> squareLambda = n -> square(n);
// Method reference — equivalent
Function<Integer, Integer> squareRef = StaticMethodRef::square;
System.out.println(squareLambda.apply(5)); // 25
System.out.println(squareRef.apply(5)); // 25
// With Stream
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
List<Integer> squares = numbers.stream()
.map(StaticMethodRef::square) // method reference
.collect(Collectors.toList());
System.out.println(squares); // [1, 4, 9, 16, 25, 36, 49, 64]
List<Integer> evens = numbers.stream()
.filter(StaticMethodRef::isEven)
.collect(Collectors.toList());
System.out.println(evens); // [2, 4, 6, 8]
// Built-in static method references
List<String> strings = Arrays.asList("3", "1", "4", "1", "5");
List<Integer> parsed = strings.stream()
.map(Integer::parseInt) // Integer.parseInt(s)
.collect(Collectors.toList());
System.out.println(parsed); // [3, 1, 4, 1, 5]
}
}
Type 2 – Instance Method of a Specific Object
import java.util.function.*;
public class InstanceSpecificRef {
public static void main(String[] args) {
String prefix = "Hello, ";
// Lambda
Function<String, String> greetLambda = name -> prefix.concat(name);
// Method reference — refers to concat() on the specific string object 'prefix'
Function<String, String> greetRef = prefix::concat;
System.out.println(greetLambda.apply("Ravi")); // Hello, Ravi
System.out.println(greetRef.apply("Java 8")); // Hello, Java 8
// Common example: System.out.println
Consumer<String> printer = System.out::println;
printer.accept("Printing via method reference");
// StringBuilder example
StringBuilder sb = new StringBuilder();
Consumer<String> appender = sb::append;
appender.accept("Java ");
appender.accept("8");
System.out.println(sb.toString()); // Java 8
// forEach with instance method reference
java.util.List<String> names = java.util.Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(System.out::println);
}
}
Type 3 – Instance Method of an Arbitrary Object (Most Common)
import java.util.*;
import java.util.stream.*;
import java.util.function.*;
public class InstanceArbitraryRef {
public static void main(String[] args) {
// String::toUpperCase — lambda: s -> s.toUpperCase()
Function<String, String> upper = String::toUpperCase;
System.out.println(upper.apply("lambda")); // LAMBDA
// String::length — lambda: s -> s.length()
Function<String, Integer> length = String::length;
// String::compareTo — lambda: (a, b) -> a.compareTo(b)
Comparator<String> comparator = String::compareTo;
List<String> words = Arrays.asList("banana", "apple", "cherry", "date");
// Using method references with streams
words.stream()
.map(String::toUpperCase)
.sorted(String::compareTo)
.forEach(System.out::println);
// APPLE, BANANA, CHERRY, DATE
// startsWith — Predicate via BiPredicate-like usage
Predicate<String> startsWithA = s -> s.startsWith("A");
long count = words.stream()
.filter(s -> s.startsWith("a") || s.startsWith("A"))
.count();
System.out.println("Count: " + count); // 1 (apple)
// Custom class example
List<Product> products = Arrays.asList(
new Product("Laptop", 75000),
new Product("Smartphone", 25000),
new Product("Keyboard", 2500)
);
// Method reference to instance method getName()
products.stream()
.map(Product::getName) // lambda: p -> p.getName()
.forEach(System.out::println); // Laptop, Smartphone, Keyboard
// Sort by price using getter reference
products.stream()
.sorted(Comparator.comparingDouble(Product::getPrice))
.map(Product::getName)
.forEach(System.out::println);
// Keyboard, Smartphone, Laptop
}
}
class Product {
private String name;
private double price;
Product(String name, double price) { this.name = name; this.price = price; }
public String getName() { return name; }
public double getPrice() { return price; }
}
Type 4 – Constructor Reference
import java.util.function.*;
import java.util.*;
import java.util.stream.*;
class Person {
private String name;
private int age;
Person(String name) { this.name = name; this.age = 0; }
Person(String name, int age) { this.name = name; this.age = age; }
@Override
public String toString() {
return name + " (" + age + ")";
}
}
public class ConstructorRef {
public static void main(String[] args) {
// Lambda — create Person from String
Function<String, Person> createLambda = name -> new Person(name);
// Constructor reference — equivalent
Function<String, Person> createRef = Person::new;
Person p1 = createLambda.apply("Alice");
Person p2 = createRef.apply("Bob");
System.out.println(p1); // Alice (0)
System.out.println(p2); // Bob (0)
// Two-argument constructor
BiFunction<String, Integer, Person> createWithAge = Person::new;
Person p3 = createWithAge.apply("Charlie", 30);
System.out.println(p3); // Charlie (30)
// Constructor reference with Stream — convert names to Person objects
List<String> names = Arrays.asList("David", "Eve", "Frank");
List<Person> people = names.stream()
.map(Person::new)
.collect(Collectors.toList());
people.forEach(System.out::println);
// David (0), Eve (0), Frank (0)
// Supplier — no-arg constructor reference
Supplier<ArrayList<String>> listSupplier = ArrayList::new;
ArrayList<String> list = listSupplier.get();
list.add("Java 8");
System.out.println(list); // [Java 8]
}
}
Method References vs Lambda – When to Use Which
| Use Method Reference when… | Use Lambda when… |
|---|---|
| The lambda body is a single method call | The body has logic beyond a single method call |
| The method already exists with the right signature | You need to combine expressions or add conditions |
Using built-in methods like System.out::println, String::length |
Capturing local variables |
// Prefer method reference — just calls one method
names.forEach(System.out::println);
list.stream().map(String::toUpperCase);
// Keep as lambda — has logic
list.stream().filter(s -> s.length() > 3 && s.startsWith("J"));
list.stream().map(s -> s.trim().toLowerCase() + "!");
Summary
Method references are shorthand for lambdas whose entire body is a single method call. The four types are: static method (Class::method), instance method on a specific object (instance::method), instance method on an arbitrary object of a type (Type::method), and constructor (Type::new). Use them to make stream pipelines and forEach calls cleaner and more readable. When in doubt, write the lambda first, then see if a method reference makes it clearer — never force one if it adds confusion.
Comments