Understanding Method References in Java 8

Understanding Method References

Method refeernces allow you to reuse existing method definitions and pass them around like Lambda expressions. They serve as a convenient shorthand for Lambda expressions that simply call a specific method. Like Lambda expressions, method references cannot exist independently and are always converted to instances of functional interfaces.

When using method references, separate the object (or class name) from the method name using the :: operator. The target reference goes before the operator, and the method name goes after. Note that only the method name is needed, with out parentheses.

There are three main categories of method references:

object::instanceMethod
Class::staticMethod
Class::instanceMethod

Static Method References

Lambda expression: (args) -> ClassName.staticMethod(args)
Method reference: ClassName::staticMethod

Example One:

@Test
public void testStaticMethodReference(){
    Comparator<integer> comparatorA = (first, second) -> Integer.compare(first, second);
    System.out.println(comparatorA.compare(10, 20));

    Comparator<integer> comparatorB = Integer::compare;
    System.out.println(comparatorB.compare(30, 20));
}
</integer></integer>

Example Two:

Integer[] numbers = {45, 12, 67, 3, 99};
Arrays.sort(numbers, Integer::compare);
System.out.println(Arrays.toString(numbers));

The Arrays.sort method signature:

static <T> void sort(T[] a, Comparator<? super T> c)
Sorts the specified array of objects according to the order induced by the specified comparator.

Instance Method References for Arbitrary Objects

Lambda expression:
(arg0, arg1) -> arg0.instanceMethod(arg1)
Where arg0 is of type ClassName

Method reference: ClassName::instanceMethod

You can use ClassName::method when the first parameter in the Lambda parameter list is the method's receiver, and the second parameter is the method's argument (or when there is no argument, such as calling a getter on a JavaBean).

Example One:

@Test
public void testArbitraryInstanceMethod(){
    BiPredicate<String, String> predicateA = (text1, text2) -> text1.equals(text2);
    System.out.println(predicateA.test("hello", "world"));

    BiPredicate<String, String> predicateB = String::equals;
    System.out.println(predicateB.test("java", "java"));
}

The Predicate.test method:

boolean test(T t, U u)
Evaluates this predicate on the given arguments.

Example Two:

List<String> names = Arrays.asList("charlie", "alice", "bob");
names.sort(String::compareTo);
System.out.println(names);

The List.sort method:

default void sort(Comparator<? super E> c)
Sorts this list using the provided comparator to compare elements.

Instance Method References for Specific Objects

Lambda expression: (args) -> object.instanceMethod(args)
Method reference: object::instanceMethod

Example One:

@Test
public void testSpecificObjectMethod(){
    PrintStream output = System.out;
    Consumer<String> consumerA = (message) -> output.println(message);
    consumerA.accept("first message");

    Consumer<String> consumerB = output::println;
    consumerB.accept("second message");
}

Example Two:

List<Integer> items = Arrays.asList(1, 2, 3, 4, 5);
items.forEach(System.out::println);

Constructor References

For existing constructors, you can create a constructor reference using the class name and the new keyword: ClassName::new

package com.example.functional;

import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

public class ConstructorReferenceDemo {
    public static void main(String[] args) {

        // Reference no-argument constructor
        Supplier<Employee> emptySupplier = Employee::new;
        Employee employeeA = emptySupplier.get();
        System.out.println(employeeA);

        // Reference single-parameter constructor
        Function<String, Employee> nameFunction = Employee::new;
        Employee employeeB = nameFunction.apply("john");
        System.out.println(employeeB);

        // Reference two-parameter constructor
        BiFunction<String, Integer, Employee> biConstructor = Employee::new;
        Employee employeeC = biConstructor.apply("jane", 35);
        System.out.println(employeeC);

        // For three or more parameters, define a custom functional interface
        TriFunction<String, Integer, String, Employee> triConstructor = Employee::new;
        Employee employeeD = triConstructor.myMethod("alice", 28, "Female");
        System.out.println(employeeD);
    }
}

@FunctionalInterface
public interface TriFunction<T, U, V, R>{
    R myMethod(T t, U u, V v);
}

public class Employee{
    private String name;
    private Integer age;
    private String gender;

    public Employee(){}

    public Employee(String name) {
        this.name = name;
    }

    public Employee(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public Employee(String name, Integer age, String gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender='" + gender + '\'' +
                '}';
    }
}

Output:

Employee{name='null', age=null, gender='null'}
Employee{name='john', age=null, gender='null'}
Employee{name='jane', age=35, gender='null'}
Employee{name='alice', age=28, gender='Female'}

Posted on Fri, 07 Aug 2026 16:53:35 +0000 by murdocsvan