Skip to main content
Advertisement

12.7 Method References

Method references are a Java 8 feature that lets you pass an existing method as if it were a lambda expression, using the :: operator. They produce more concise and readable code than lambdas.

1. Why Method References?

List<String> names = List.of("Alice", "Bob", "Charlie");

names.forEach(name -> System.out.println(name)); // Lambda
names.forEach(System.out::println); // Method reference (cleaner!)

When a lambda simply calls an existing method, replace it with a method reference.


2. Four Types of Method References

Type 1: Static Method Reference

ClassName::staticMethodName

Function<String, Integer> parser = Integer::parseInt;  // Integer.parseInt(s)
System.out.println(parser.apply("42")); // 42

List<String> numberStrings = List.of("1", "2", "3", "4", "5");
List<Integer> numbers = numberStrings.stream()
.map(Integer::parseInt)
.collect(Collectors.toList());

Type 2: Bound Instance Method Reference

instance::instanceMethodName

String prefix = "Hello, ";
Function<String, String> greeter = prefix::concat; // prefix.concat(s)
System.out.println(greeter.apply("Java")); // Hello, Java

Type 3: Unbound Instance Method Reference

ClassName::instanceMethodName The first parameter becomes the receiver object.

Function<String, String>    upper  = String::toUpperCase; // s -> s.toUpperCase()
Function<String, Integer> length = String::length; // s -> s.length()
Predicate<String> blank = String::isEmpty; // s -> s.isEmpty()
BiPredicate<String, String> starts = String::startsWith; // (s1,s2) -> s1.startsWith(s2)

System.out.println(upper.apply("hello")); // HELLO
System.out.println(starts.test("hello", "he")); // true

List<String> words = List.of("Hello", "World", "Java", "");
words.stream()
.filter(Predicate.not(String::isEmpty))
.map(String::toLowerCase)
.sorted(String::compareTo)
.forEach(System.out::println); // hello, java, world

Type 4: Constructor Reference

ClassName::new

Supplier<ArrayList<String>>     listMaker = ArrayList::new;
Function<String, StringBuilder> sbMaker = StringBuilder::new;

List<String> newList = listMaker.get();
StringBuilder sb = sbMaker.apply("Hello");

// Useful with stream toArray
String[] words = List.of("apple", "banana", "cherry").stream()
.filter(s -> s.length() > 5)
.toArray(String[]::new); // new String[n] handled automatically

3. Comprehensive Example

record Person(String name, int age, String city) {
boolean isAdult() { return age >= 18; }
String greeting() { return "Hello, I'm " + name + "!"; }
}

List<Person> people = List.of(
new Person("Alice", 25, "Seoul"),
new Person("Bob", 16, "Busan"),
new Person("Charlie", 30, "Seoul")
);

// Filter adults and print greetings
people.stream()
.filter(Person::isAdult) // p -> p.isAdult()
.map(Person::greeting) // p -> p.greeting()
.forEach(System.out::println);

// Sort by name
people.stream()
.map(Person::name)
.sorted(String::compareTo)
.forEach(System.out::println);

// Find youngest
people.stream()
.min(Comparator.comparingInt(Person::age))
.ifPresent(p -> System.out.println("Youngest: " + p.name()));

Pro Tip

Method reference vs lambda: when to use which?

  • Method reference: Lambda body only calls one existing method → more readable
  • Lambda: Needs argument transformation, multiple operations, or complex conditions → lambda is clearer
// Method references are clearer here
.map(String::toUpperCase) // ✅ vs s -> s.toUpperCase()
.filter(Objects::nonNull) // ✅ vs s -> s != null

// Lambdas are clearer here
.filter(s -> s.length() > 3 && s.startsWith("A")) // complex condition
.map(s -> "[" + s + "]") // transformation logic
Advertisement