«1. Обзор

В Java 8 появилась концепция ссылок на методы. Мы часто видим их похожими на лямбда-выражения.

Однако ссылки на методы и лямбда-выражения — это не совсем одно и то же. В этой статье мы покажем, чем они отличаются и каковы риски их неправильного использования.

2. Синтаксис лямбда-выражений и ссылок на методы

Для начала давайте рассмотрим несколько примеров лямбда-выражений:

Runnable r1 = () -> "some string".toUpperCase();
Consumer<String> c1 = x -> x.toUpperCase();

И несколько примеров ссылок на методы:

Function<String, String> f1 = String::toUpperCase;
Runnable r2 = "some string"::toUpperCase;
Runnable r3 = String::new;

Эти примеры могут сделать мы думаем о ссылках на методы как об укороченной нотации лямбда-выражений.

Но давайте взглянем на официальную документацию Oracle. Здесь мы можем найти интересный пример:

(test ? list.replaceAll(String::trim) : list) :: iterator

Как мы видим, Спецификация языка Java позволяет нам использовать различные виды выражений перед оператором двойного двоеточия. Часть перед :: называется целевой ссылкой.

Далее мы обсудим процесс оценки ссылки на метод.

3. Оценка ссылки на метод

Что произойдет, когда мы запустим следующий код?

public static void main(String[] args) {
    Runnable runnable = (f("some") + f("string"))::toUpperCase;
}

private static String f(String string) {
    System.out.println(string);
    return string;
}

Мы только что создали объект Runnable. Ни больше ни меньше. Однако вывод:

some
string

Это произошло потому, что целевая ссылка оценивается при первом обнаружении объявления. Следовательно, мы потеряли желанную лень. Целевая ссылка также оценивается только один раз. Итак, если мы добавим эту строку к приведенному выше примеру:

runnable.run()

Мы не увидим никакого вывода. Что насчет следующего дела?

SomeWorker worker = null;
Runnable workLambda = () -> worker.work() // ok
Runnable workMethodReference = worker::work; // boom! NullPointerException

Объяснение, приведенное в документации, упомянутой ранее:

«Выражение вызова метода (§15.12), которое вызывает метод экземпляра, создает исключение NullPointerException, если целевая ссылка равна null».

лучший способ предотвратить непредвиденные ситуации — никогда не использовать доступ к переменным и сложные выражения в качестве целевых ссылок.

Хорошей идеей может быть использование ссылок на методы только как аккуратное, короткое обозначение для его лямбда-эквивалента. Наличие только имени класса перед оператором :: гарантирует безопасность.

4. Заключение

В этой статье мы узнали о процессе оценки ссылок на методы.

Мы знаем риски и правила, которым мы должны следовать, чтобы не быть неожиданно удивленными поведением нашего приложения.