JavaRush /Курси /JAVA 25 SELF /Method References (::): посилання на методи

Method References (::): посилання на методи

JAVA 25 SELF
Рівень 21 , Лекція 1
Відкрита

1. Вступ

Method reference (або українською — посилання на метод) — це спеціальний синтаксис у Java, що дає змогу передавати вже наявний метод (або конструктор) як реалізацію функціонального інтерфейсу. Метод можна «передати» туди, де очікується лямбда-вираз, якщо сигнатури збігаються.

Синтаксис:

Клас::метод
об’єкт::метод
Клас::new

Якщо лямбда — це мініфункція «на льоту», то method reference — це просто «передати вже наявний метод». Це схоже на ситуацію, коли замість переписування рецепта ви даєте посилання на сторінку з рецептом.

Приклад «на пальцях»

Замість цього:

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

Можна так:

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

Виглядає лаконічно, чи не так?

2. Види посилань на методи

У посилань на методи є чотири основні форми. Ось вони — цього цілком достатньо.

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

Синтаксис: Клас::статичнийМетод

Приклад:

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

Те саме — через лямбду:

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

Посилання на нестатичний метод об’єкта

Синтаксис: об’єкт::метод

Приклад:

PrintStream printer = System.out;
Consumer<String> consumer = printer::println;
consumer.accept("Привіт, світе!");

Еквівалентно лямбді:

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

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

Синтаксис: Клас::метод

Тут перший параметр функціонального інтерфейсу стає об’єктом, на якому викликається метод.

Приклад:

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

Тут String::length перетворюється на функцію: (String s) -> s.length()

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

Синтаксис: Клас::new

Приклад:

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

Еквівалентно лямбді:

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

3. Коли використовувати method reference?

Method reference доречно застосовувати, коли лямбда лише викликає наявний метод без додаткової логіки — так код стає коротшим і читабельнішим.

Приклад: сортування списку

Замість цього:

List<String> names = Arrays.asList("Іван", "Петро", "Анна");
names.sort((a, b) -> a.compareToIgnoreCase(b));

Можна так:

names.sort(String::compareToIgnoreCase);

Приклад: обробка колекцій

Замість:

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

Ще коротше:

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

Приклад: перетворення елементів

Замість:

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

Можна:

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

Детальніше про Stream API та утилітний клас Collectors ви дізнаєтеся на 30‑му рівні :P

4. Порівняння method reference і лямбда-виразів

Еквівалентність

Method reference і лямбда-вираз часто взаємозамінні. Обидва реалізують функціональний інтерфейс, якщо сигнатури збігаються.

Приклад:

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

Коли краще використовувати method reference?

  • Коли лямбда лише викликає наявний метод без додаткової логіки.
  • Щоб підвищити читабельність, особливо в довгих ланцюжках викликів.
  • Коли потрібно явно показати: «тут просто виклик методу».

Коли method reference не підходить?

  • Якщо потрібна додаткова логіка (валідація, умови, обробка помилок).
  • Якщо параметри потрібно перетворити перед викликом методу.

Приклад:

list.forEach(s -> {
    if (s != null) System.out.println(s);
});
// Тут посилання на метод не підійде — лише лямбда.

5. Практика: переписуємо лямбда-вирази з використанням посилань на методи

Приклад 1: Виводимо імена тварин

List<String> animals = Arrays.asList("Кіт", "Собака", "Папуга");
animals.forEach(animal -> System.out.println(animal));

Стає:

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

Приклад 2: Перетворюємо рядки на числа

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

Стає:

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

Приклад 3: Сортування об’єктів за ім’ям

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

Стає:

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

Тут Animal::getName — посилання на нестатичний метод класу.

Приклад 4: Створення об’єктів через конструктор

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

Стає:

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

6. Як працює зіставлення сигнатур

Посилання на метод можна використовувати лише тоді, коли сигнатура цього методу збігається з абстрактним методом функціонального інтерфейсу.

@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
    }
}

Тут Demo::intToHex підходить, тому що приймає int і повертає String.

7. Method reference і конструктори з параметрами

Якщо конструктор приймає параметри, method reference усе одно можна використовувати — за умови збігу сигнатур.

@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("Барсик");
System.out.println(cat.getName()); // Барсик

8. Типові помилки при використанні посилань на методи

Помилка № 1: Невідповідність сигнатур.
Якщо сигнатура методу не збігається з абстрактним методом інтерфейсу, компілятор видасть помилку. Наприклад, інтерфейс очікує два параметри, а посилання вказує на метод з одним параметром.

Помилка № 2: Спроба використати посилання на нестатичний метод класу без об’єкта.
Коли використовуєте форму Клас::метод, перший параметр інтерфейсу стає об’єктом виклику. Якщо переплутати кількість або порядок параметрів — отримаєте помилку зіставлення.

Помилка № 3: Використання method reference там, де потрібна додаткова логіка.
Якщо потрібна умова, логування або обробка винятків — використовуйте лямбду, а не посилання на метод.

Помилка № 4: Посилання на перевантажені методи.
Якщо в класі кілька методів з однаковою назвою, компілятор може не зрозуміти, який із них обрати. Іноді допомагає явне зазначення типу функціонального інтерфейсу.

Помилка № 5: Використання посилання на нестатичний метод без об’єкта.
Наприклад, String::toUpperCase коректно працює в map, тому що перший параметр — сам об’єкт String. Але поза відповідним контекстом, де очікується статичний метод, це призведе до помилки.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ