Освой Java играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
В Java 8 появились лямбда-выражения. Поначалу они кажутся какой-то магией.

Давайте разбираться.
После того, как вы включили поддержку Java 8, вы можете использовать лямбда-выражения.
Сначала рассмотрим, как студия пытается помочь использовать новые возможности.
Допустим, у нас есть код для щелчка кнопки.
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "Clicked", Toast.LENGTH_SHORT).show();
}
});
Студия показывает имя анонимного класса серым цветом и выводит подсказку, что можно заменить код на лямбда-выражение.

Получится.
button.setOnClickListener(v ->
Toast.makeText(MainActivity.this, "Clicked", Toast.LENGTH_SHORT).show()
);
Это подходит для старых проектов,когда код уже написан в старом стиле. Если вы пишете новый код, используя автозавершение, то студия сразу же предлагает использовать лямбда-выражение.

Когда код в студии использует лямбда-выражение, то слева на информационном желобе рядом с нумерацией строк выводится специальный значок с греческим символом лямбды λ. Щелчок по нему перенесёт в суперкласс.
Как же это работает? Нужно понимать, что лямбда-выражение является синтаксическим сахаром, который уменьшает количество кода. Понадобится некоторое время, чтобы начать понимать новый код.
Лямбда-выражение можно применить только к интерфейсу с единственным абстрактным методом (также могут быть другие методы, но не абстрактные). Интерфейс OnClickListener с единственным абстрактным методом onClick() как раз и является таким. Зная, что интерфейс имеет только один метод, мы можем опустить его имя и его модификатор. Вычёркиваем лишнее.
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "Clicked", Toast.LENGTH_SHORT).show();
}
});
У метода используется один параметр типа View. Раз мы знаем этот тип, то тоже опускаем его, оставляя только имя переменной view или v для совсем ленивых.
(View v)
То, что мы пишем в теле метода, пишется после комбинации символов ->.
Вот таким образом и трансформируется старый код в новый.
Возьмём теперь код щелчка на элементе списка.
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Log.d(TAG, "Position: " + position);
Log.d(TAG, "Id: " + id);
}
});
У интерфейса AdapterView.OnItemClickListener есть единственный метод onItemClick() с четырьмя параметрами. Следуем такому алгоритму - убираем имя метода, а в параметрах убираем типы переменных. Получится следующее:
listView.setOnItemClickListener((parent, view, position, id) -> {
Log.d(TAG, "Position: " + position);
Log.d(TAG, "Id: " + id);
}
Ещё один вариант для интерфейса Runnable с единственным методом run().
Runnable runnable = new Runnable() {
@Override
public void run() {
Log.d(TAG, "From Runnable");
}
};
new Thread(runnable).start();
У метода нет параметров, поэтому оставляем только скобки.
Runnable runnable = () -> Log.d(TAG, "From Runnable");
new Thread(runnable).start();
Общий формат лямбда-выражения: список параметров через запятую, далее ->, а затем тело выражения.
param1, param2, paramN -> { // body }
Если тело состоит из одной строчки, то фигурные скобки можно опустить. В примерах выше есть два варианта.
Мы рассмотрели случаи, когда метод имел возвращаемое значение void.
Если метод что-то возвращает, то здесь также доступна оптимизация. Например, если можно составить выражение в одну строку для возвращаемого значения, то return можно убрать.
// Создадим собственный интерфейс
public interface Calculator {
int calculate(int a, int b);
}
Тогда можно написать так.
Calculator calculator = (a, b) -> a + b;
Выражение a + b соответствует выражению {return a + b;}.
Очень часто используется лямбда-выражение, которое вызывает метод от имени своего параметра. Например, мы хотим, чтобы лямбда-выражение получало имя кота:
cat -> cat.getName()
Учитывая популярность данного кода, решили ввести сокращённый синтаксис, который позволяет повторно использовать существующий метод. Синтаксис называется ссылкой на метод. Воспользовавшись ссылкой на метод, предыдущее лямбда-выражение можно переписать так:
Cat::getName
В общем случае ссылка на методы имеет вид ClassName::methodName. Хотя это и метод, указывать скобки не следует, так как мы ничего не вызываем. Это просто эквивалент лямбда-выражения, вызов которого приведёт к вызову метода. Ссылки на методы можно использовать всюду, где допустимы лямбда-выражения.
Сокращённый синтаксис применим и к конструктору.
Cat::new
Так можно создавать и массивы. Например, так создаётся массив строк:
String[]::new
Операция :: отделяет имя метода от имени класса или объекта, доступны три варианта.
Выражение System.out::println равнозначно лямбда-выражению x -> System.out.println(x).
Выражение Math::pow равнозначно лямбда-выражению (x, y) -> Math.pow(x, y).
Выражение String::compareToIgnoreCase равнозначно лямбда-выражению (x, y) -> x.compareToIgnoreCase(y).
Интерфейс с одним абстрактным методом можно снабдить аннотацией @FunctionalInterface, чтобы уменьшить число ошибок. Компилятор заметит попытку добавить второй абстрактный метод и предупредит вас.
В стандартной библиотеке Java имеется целый ряд функциональных интерфейсов из пакета java.util.function.
BiFunction<T, U, R> - описывает функции с помощью параметров типа Т и U и возвращаемого типа R.
Predicate<T>
Есть список значений и их нужно перебрать. Старый способ.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
for (int number : numbers) {
System.out.println(number);
}
Мы последовательно перебираем все числа по одному и выводим их на экран. С лямбда-выражением можно переписать код, который работает по другому. Мы сразу берём все числа и скармливаем методу.
numbers.forEach((Integer value) -> System.out.println(value)); // первый способ
numbers.forEach(value -> System.out.println(value)); // укорачиваем код, убирая тип
numbers.forEach(System.out::println); // ещё короче, используя оператор ::
Иногда нужно не только перебрать все числа в списке, но и что-то с ними сделать. Например, сложить их. Но задачи могут различаться, может вам нужно сложить все числа, может нужно сложить только чётные числа, а может только числа, которые больше определённого значения. Раньше нам пришлось бы создавать три отдельных метода для каждой задачи. Теперь можно создать один универсальный метод с предикатом.
public void onClick(View view) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int result;
result = sumAll(numbers, n -> true); // суммируем все числа
System.out.println(result);
result = sumAll(numbers, n -> n % 2 == 0); // суммируем чётные числа
System.out.println(result);
result = sumAll(numbers, n -> n > 3); // суммируем числа, которые больше трёх
System.out.println(result);
}
public int sumAll(List<Integer> numbers, Predicate<Integer> p) {
int total = 0;
for (int number : numbers) {
if (p.test(number)) {
total += number;
}
}
return total;
}
Пример можно переписать с использованием нового Stream API, где есть специальные возможности для работы с коллекциями.