1. Введение
Method reference (или по-русски — ссылка на метод) — это специальный синтаксис в Java, позволяющий передавать уже существующий метод (или конструктор) в качестве реализации функционального интерфейса. Метод можно "передать" туда, где ожидается лямбда-выражение, если сигнатуры совпадают.
Синтаксис:
Класс::метод
объект::метод
Класс::new
Если лямбда — это мини-функция "на лету", то method reference — это просто «передай уже имеющийся метод». Это похоже на то, как вместо переписывания рецепта вы даёте ссылку на страницу с рецептом.
Пример на пальцах
Вместо этого:
list.forEach(s -> System.out.println(s));
Можно так:
list.forEach(System.out::println);
Выглядит лаконично, не правда ли?
2. Виды method references
У ссылок на методы четыре основные формы. Вот они — и больше ничего не нужно.
Ссылка на статический метод
Синтаксис: Класс::статическийМетод
Пример:
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);
});
// Здесь method reference не подойдёт, только лямбда.
5. Практика: переписываем лямбда-выражения с использованием method references
Пример 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. Как работает сопоставление сигнатур
Method reference можно использовать только тогда, когда сигнатура метода совпадает с абстрактным методом функционального интерфейса.
@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. Типичные ошибки при использовании method references
Ошибка №1: Несоответствие сигнатур.
Если сигнатура метода не совпадает с абстрактным методом интерфейса, компилятор выдаст ошибку. Например, интерфейс ожидает два параметра, а ссылка указывает на метод с одним параметром.
Ошибка №2: Попытка использовать ссылку на нестатический метод класса без объекта.
Когда используете форму Класс::метод, первый параметр интерфейса становится объектом вызова. Если перепутать количество или порядок параметров — получите ошибку сопоставления.
Ошибка №3: Использование method reference там, где нужна дополнительная логика.
Если требуется условие, логирование или обработка исключений — используйте лямбду, а не ссылку на метод.
Ошибка №4: Ссылки на перегруженные методы.
Если у класса несколько методов с одинаковым именем, компилятор может не понять, какой из них выбрать. Иногда помогает явное указание типа функционального интерфейса.
Ошибка №5: Использование ссылки на нестатический метод без объекта.
Например, String::toUpperCase корректно работает в map, потому что первый параметр — сам объект String. Но вне подходящего контекста, где ожидается статический метод, это приведёт к ошибке.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ