CodeGym /Courses /JAVA 25 SELF /Method References (::): references to methods

Method References (::): references to methods

JAVA 25 SELF
Level 21, Lesson 1
Available

1. Introduction

A method reference is special syntax in Java that lets you pass an existing method (or constructor) as an implementation of a functional interface. You can “pass” a method where a lambda expression is expected if the signatures match.

Syntax:

Class::method
object::method
Class::new

If a lambda is a tiny “on-the-fly” function, then a method reference is simply “pass an already existing method”. It’s like giving a link to a recipe page instead of rewriting the recipe.

An example in simple terms

Instead of this:

list.forEach(s -> System.out.println(s));

You can do this:

list.forEach(System.out::println);

Looks concise, doesn’t it?

2. Types of method references

There are four main forms of method references. Here they are—and that’s all you need.

Reference to a static method

Syntax: Class::staticMethod

Example:

Function<Integer, String> intToString = String::valueOf;
System.out.println(intToString.apply(123)); // "123"

The same using a lambda:

Function<Integer, String> intToString = i -> String.valueOf(i);

Reference to an instance method of an object

Syntax: object::method

Example:

PrintStream printer = System.out;
Consumer<String> consumer = printer::println;
consumer.accept("Hello, world!");

Equivalent to a lambda:

Consumer<String> consumer = s -> printer.println(s);

Reference to an instance method of a class

Syntax: Class::method

Here, the first parameter of the functional interface becomes the instance on which the method is invoked.

Example:

Function<String, Integer> stringLength = String::length;
System.out.println(stringLength.apply("Java")); // 4

Here, String::length becomes the function: (String s) -> s.length()

Reference to a constructor

Syntax: Class::new

Example:

Supplier<ArrayList<String>> listSupplier = ArrayList::new;
ArrayList<String> list = listSupplier.get();

Equivalent to a lambda:

Supplier<ArrayList<String>> listSupplier = () -> new ArrayList<>();

3. When to use method references?

A method reference is handy when a lambda simply calls an existing method with no additional logic—the code becomes shorter and more readable.

Example: sorting a list

Instead of this:

List<String> names = Arrays.asList("John", "Peter", "Anna");
names.sort((a, b) -> a.compareToIgnoreCase(b));

You can do this:

names.sort(String::compareToIgnoreCase);

Example: processing collections

Instead of:

list.forEach(s -> System.out.println(s));

Make it even shorter:

list.forEach(System.out::println);

Example: transforming elements

Instead of:

List<String> numbers = Arrays.asList("1", "2", "3");
List<Integer> ints = numbers.stream()
    .map(s -> Integer.parseInt(s))
    .collect(Collectors.toList());

Use this:

List<Integer> ints = numbers.stream()
    .map(Integer::parseInt)
    .collect(Collectors.toList());

You’ll learn more about the Stream API and the utility class Collectors at level 30 :P

4. Comparison of method references and lambda expressions

Equivalence

Method references and lambda expressions are often interchangeable. Both implement a functional interface if their signatures match.

Example:

Consumer<String> c1 = s -> System.out.println(s);
Consumer<String> c2 = System.out::println;

When is it better to use a method reference?

  • When a lambda simply calls an existing method with no extra logic.
  • To improve readability, especially in long call chains.
  • When you want to make it explicit: “this is just a method call”.

When is a method reference not a good fit?

  • If you need additional logic (validation, conditions, error handling).
  • If parameters need to be transformed before calling the method.

Example:

list.forEach(s -> {
    if (s != null) System.out.println(s);
});
// A method reference won’t work here—only a lambda.

5. Practice: rewriting lambda expressions using method references

Example 1: printing animal names

List<String> animals = Arrays.asList("Cat", "Dog", "Parrot");
animals.forEach(animal -> System.out.println(animal));

Becomes:

animals.forEach(System.out::println);

Example 2: converting strings to numbers

List<String> numbers = Arrays.asList("10", "20", "30");
List<Integer> ints = numbers.stream()
    .map(s -> Integer.parseInt(s))
    .collect(Collectors.toList());

Becomes:

List<Integer> ints = numbers.stream()
    .map(Integer::parseInt)
    .collect(Collectors.toList());

Example 3: sorting objects by name

List<Animal> animalList = ...;
animalList.sort((a, b) -> a.getName().compareTo(b.getName()));

Becomes:

animalList.sort(Comparator.comparing(Animal::getName));

Here, Animal::getName is a reference to an instance method of a class.

Example 4: creating objects via a constructor

Supplier<Dog> dogFactory = () -> new Dog();
Dog dog = dogFactory.get();

Becomes:

Supplier<Dog> dogFactory = Dog::new;
Dog dog = dogFactory.get();

6. How signature matching works

You can use a method reference only when the method’s signature matches the abstract method of the functional interface.

@FunctionalInterface
interface IntToString {
    String convert(int value);
}

public class Demo {
    public static String intToHex(int value) {
        return Integer.toHexString(value);
    }

    public static void main(String[] args) {
        IntToString converter = Demo::intToHex;
        System.out.println(converter.convert(255)); // ff
    }
}

Here, Demo::intToHex fits because it takes an int and returns a String.

7. Method references and constructors with parameters

If a constructor takes parameters, you can still use a method reference—as long as the signatures match.

@FunctionalInterface
interface AnimalFactory {
    Animal create(String name);
}

class Animal {
    private String name;
    public Animal(String name) { this.name = name; }
    public String getName() { return name; }
}

AnimalFactory factory = Animal::new;
Animal cat = factory.create("Kitty");
System.out.println(cat.getName()); // Kitty

8. Common mistakes when using method references

Error #1: Signature mismatch.
If a method’s signature does not match the interface’s abstract method, the compiler will report an error. For example, the interface expects two parameters, but the reference points to a method with one parameter.

Error #2: Attempting to use a reference to an instance method of a class without an instance.
When you use the form Class::method, the first parameter of the interface becomes the receiver object. If you mix up the number or order of parameters, you’ll get a matching error.

Error #3: Using a method reference where additional logic is needed.
If you need a condition, logging, or exception handling—use a lambda, not a method reference.

Error #4: References to overloaded methods.
If a class has multiple methods with the same name, the compiler may not know which one to choose. Sometimes explicitly specifying the functional interface type helps.

Error #5: Using a reference to an instance method without an object.
For example, String::toUpperCase works correctly in map because the first parameter is the String instance itself. But outside an appropriate context that expects a static method, this will lead to an error.

1
Task
JAVA 25 SELF, level 21, lesson 1
Locked
Device Identifier Converter 📟
Device Identifier Converter 📟
1
Task
JAVA 25 SELF, level 21, lesson 1
Locked
Custom Logging System 📝
Custom Logging System 📝
1
Task
JAVA 25 SELF, level 21, lesson 1
Locked
Warehouse inventory organization 🍎🍌🍐
Warehouse inventory organization 🍎🍌🍐
1
Task
JAVA 25 SELF, level 21, lesson 1
Locked
Virtual Pet Factory 🐾
Virtual Pet Factory 🐾
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION