Method References (посилання на методи) з’явилися у Java 8. Вони дозволяють передавати методи (або конструктори) як аргументи, коли очікується функціональний інтерфейс. Це альтернатива лямбдам, у яких вся робота зводиться до виклику одного методу. Посилання на метод робить код компактнішим і легшим для читання, зберігаючи ту ж саму логіку виконання.

Розглянемо приклад:

public class Main {

    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob", "Charlie");

        // Використання lambda виразу
        names.forEach(name -> System.out.println(name));

        // Використання посилання на метод
        names.forEach(System.out::println);
    }
}

У цьому прикладі:

  • Метод forEach очікує на вхід функціональний інтерфейс Consumer<T> з єдиним методом accept(T t). У нашому випадку T – це String.
  • Ми можемо передати лямбда-вираз name -> System.out.println(name), який приймає параметр і передає його в метод println.
  • Або замінити його на посилання System.out::println. Це посилання не викликає метод одразу, а лише передає його як реалізацію інтерфейсу. Компілятор визначає, що сигнатура println(String) сумісна з методом accept(String), і виклик відбувається для кожного елемента списку.

Типи Method References у Java

У Java існує чотири основні види посилань на методи:

  • посилання на статичний метод
  • посилання на метод певного об’єкта 
  • посилання на метод будь-якого об’єкта певного типу
  • посилання на конструктор

Далі буде розглянуто кожен тип на практичних прикладах.

Посилання на статичний метод

У цьому випадку використовується посилання на статичний метод класу:

public class Main {

    public static void main(String[] args) {
        List<String> numbers = List.of("1", "2", "3", "4", "5");

        // Використання лямбда-виразу
        List<Integer> result1 = numbers.stream()
                .map(number -> Integer.parseInt(number))
                .toList();

        // Використання посилання на статичний метод
        List<Integer> result2 = numbers.stream()
                .map(Integer::parseInt)
                .toList();

        System.out.println(result1); // [1, 2, 3, 4, 5]
        System.out.println(result2); // [1, 2, 3, 4, 5]
    }
}

У цьому прикладі Integer::parseInt є посиланням на статичний метод класу Integer, який використовується для перетворення кожного рядка у число.

Посилання на метод певного об’єкта

Цей тип використовується, коли потрібно викликати метод вже створеного об’єкта:

public class Greeter {

    public void greet(String name) {
        System.out.println("Hello, " + name + "!");
    }
}

public class Main {

    public static void main(String[] args) {
        Greeter greeter = new Greeter();
        List<String> names = List.of("Dan", "Jen", "Isabella", "Juliana");

        // Використання лямбда-виразу
        names.forEach(name -> greeter.greet(name));

        // Використання посилання на метод
        names.forEach(greeter::greet);
    }
}

У цьому прикладі greeter::greet є посиланням на метод конкретного екземпляра класу Greeter, яке використовується для виклику greet для кожного елемента списку.

Посилання на метод будь-якого об’єкта певного типу

Цей тип використовується, коли потрібно викликати метод на кожному об’єкті певного типу. У такому випадку Java автоматично підставляє об’єкт, до якого застосовується метод:

public class Main {

    public static void main(String[] args) {
        List<String> names = new ArrayList<>(List.of("Dan", "Jen", "Isabella", "Juliana"));

        // Використання лямбда-виразу
        names.sort((a, b) -> a.compareToIgnoreCase(b));

        // Використання посилання на метод
        names.sort(String::compareToIgnoreCase);
    }
}

У цьому прикладі String::compareToIgnoreCase є посиланням на метод будь-якого об’єкта типу String. Воно еквівалентне лямбда-виразу (a, b) -> a.compareToIgnoreCase(b) і використовується як реалізація функціонального інтерфейсу Comparator<String>.

Посилання на конструктор

Цей тип використовується, коли потрібно створювати нові об’єкти: замість лямбда-виразу, який викликає конструктор, можна передати посилання на нього:

public class Person {

    private String name;

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

    @Override
    public String toString() {
        return "Person{name='" + name + "'}";
    }
}

public class Main {

    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob", "Charlie");

        // Використання лямбда-виразу
        List<Person> people1 = names.stream()
                .map(name -> new Person(name))
                .toList();

        // Використання посилання на конструктор
        List<Person> people2 = names.stream()
                .map(Person::new)
                .toList();

        people2.forEach(System.out::println);
    }
}

/*
Output:
Person{name='Alice'}
Person{name='Bob'}
Person{name='Charlie'}
*/

У цьому прикладі Person::new є посиланням на конструктор класу Person. Java автоматично передає кожен рядок з names як аргумент у конструктор Person(String name).

Підсумок

Посилання на методи – це зручний інструмент у Java, що допомагає писати чистий і зрозумілий код. Вони особливо корисні у випадках, коли лямбда-вираз просто викликає існуючий метод, роблячи запис більш компактним і наочним.