Возможно, вы уже знакомы с лямбда-выражением, если вы работаете с Javascript или другими языками с неродной типизацией. В этих языках вы можете просто создать лямбда-выражение (аргумент и тело возврата) и обращаться с ним как с переменной (вы можете передать его в методы и аргументы функций). Пример в JS ES6:

// create ES6 anonymous function and use it
printer = (t) => console.log(t);
printer(“Hello”);

Как сделать то же самое на Java

Java также представила лямбда-выражение с версии 8 с похожим синтаксисом выражений, но разница в том, что каждая переменная в Java имеет тип (собственный или класс/ интерфейс), и все должно оставаться в классе (никаких функций, только методы). Итак, как вы можете догадаться, типом лямбда-выражения является любой интерфейс с одним методом. Вот пример с универсальным типом и методом с произвольным названием accept():

interface MyInterface<T> {
    void accept(T t);
}

Это тип, соответствующий лямбда-функции, которая принимает переменную и что-то с ней делает, ничего не возвращая (void). Давайте сначала реализуем интерфейс не-лямбда-способ вывода значения на консоль:

public class LambdaSample {

    public static void main(String[] args) {
        MyInterface<String> printer1 = new MyInterface<String>() {
            public void accept(String t) {
                System.out.println(t);
            }
        };

        printer1.accept("hello");
    }
}

По сути, мы создали реализацию и запустили ее метод. Эквивалент (но не то же самое с точки зрения байт-кода) с лямбда-выражением Java выглядит следующим образом:

MyInterface<String> printer2 = t -> System.out.println(t);
printer2.accept("hello");

В обоих случаях мы создали реализацию, а затем вызвали ее метод, передав аргумент. Метод просто делал то, для чего был реализован (вывод значения).

Примечание 1: MyInterface‹String› — это тип выражения, который может использоваться в методе, например.

private static void methodAcceptingInterface(MyInterface mi) {
    mi.accept("hello from method");
}

Примечание 2: лямбда-выражение может указывать тип аргумента, а также требовать фигурные скобки и возвращаемый тип, если имеется более одной инструкции. Существует также более короткий синтаксис, который позволяет использовать этот пример, его также можно записать как

Consumer<String> printer = System.out::println

При необходимости обратитесь к документации.

Существующие интерфейсы Java

Как вы могли заметить, написанный нами интерфейс можно повторно использовать для любого другого лямбда-выражения, которое принимает тип и возвращает void. В Java уже есть несколько интерфейсов (называемых функциональными интерфейсами) с разными общими входными и выходными данными (см. список здесь). В этом конкретном случае интерфейс с методом accept(), который ничего не возвращает, называется Consumer и может использоваться вместо нашего интерфейса. Таким образом, весь приведенный выше код может быть записано как:

import java.util.function.Consumer;

public class LambdaSample {

    public static void main(String[] args) {
        Consumer<String> printer = (String i) -> System.out.println(i);
        printer.accept("hello");
    }
}

Пример передачи лямбда-функции собственному методу Java

Метод Collections:removeIf() принимает функциональный интерфейс Predicate‹T› (функциональный интерфейс, принимающий тип и возвращающий логическое значение). Как вы понимаете, этот метод удаляет объект, который возвращает true при передаче в нашу реализацию (выражение lamba) интерфейса. Вот пример, который создает список чисел от 1 до 5 и удаляет четные:

List<Integer> list = Stream.of(1,2,3,4,5)
        .collect(Collectors.toList());
list.removeIf(i -> i%2 == 0);
System.out.println(list); //[1, 3, 5]

Спасибо за прочтение и хлопайте, если полезно