Лямбда-выражения, которые Xiaobai может понять

Java

Зачем вводить лямбда-выражения

Что бы вы сделали до Java 8, если бы захотели передать класс реализации интерфейса в качестве параметра методу? Либо создайте класс для реализации интерфейса, а затем создайте объект и передайте его при вызове метода, либо используйте анонимный класс для упрощения некоторого кода. Возьмем в качестве примера создание потока и печать строки журнала. Анонимная функция записывается следующим образом:

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("小白也能看懂了的Lambda 表达式");
    }
}).start();

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

new Thread(() -> System.out.println("小白也能看懂了的Lambda 表达式")).start();

Структура синтаксиса лямбда-выражения

Лямбда-выражения обычно записываются с использованием синтаксиса (param)->(body).Основной формат выглядит следующим образом.

//没有参数
() -> body
// 1个参数
(param) -> body
// 或
(param) ->{ body; }
// 多个参数
(param1, param2...) -> { body }
// 或
(type1 param1, type2 param2...) -> { body }

Общие лямбда-выражения следующие:

// 无参数,返回值为字符串“小白也能看懂了的Lambda 表达式”
() -> "小白也能看懂了的Lambda 表达式";

// 1个String参数,直接打印结果
(System.out::println);
// 或
(String s) -> System.out.print(s)

// 1个参数(数字),返回2倍值  
x -> 2 * x;

// 2个参数(数字),返回差值
(x, y) -> x – y

// 2个int型整数,返回和值  
(int x, int y) -> x + y

В отличие от приведенного выше примера, давайте суммируем структуру лямбда-выражения:

  • Лямбда-выражения могут иметь от 0 до n параметров.
  • Типы параметров могут быть объявлены явно, или компилятор может автоматически определить тип из контекста. Например, (int x) и (x) эквивалентны.
  • Несколько параметров заключены в круглые скобки и разделены запятыми. Параметр может быть без скобок.
  • Отсутствие аргументов обозначается пустыми скобками.
  • Тело лямбда-выражения может содержать ноль, одно или несколько операторов и должно содержать оператор возвращаемого значения, если есть возвращаемое значение. Фигурные скобки можно опустить, если они только одни. Если их несколько, они должны быть заключены в фигурные скобки (кодовые блоки).

функциональный интерфейс

Функциональный интерфейс (Functional Interface) — это название в Java8 для особого типа интерфейса. Этот тип интерфейса определяет только唯一的抽象方法(за исключением неявных общедоступных методов объекта Object), поэтому вначале это также интерфейс типа SAM (Single Abstract Method).

Например, java.lang.Runnable в приведенном выше примере — это функциональный интерфейс, и в нем определен только один абстрактный метод void run(), а на интерфейсе есть аннотация @FunctionalInterface.

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

Аннотация @FunctionalInterface используется для указания того, что интерфейс должен соответствовать спецификации функционального интерфейса, и может быть только один абстрактный метод в дополнение к неявным общедоступным методам объекта Object. Конечно,如果某个接口只定义一个抽象方法,不使用该注解也是可以使用Lambda表达式的,但是没有该注解的约束,后期可能会新增其他的抽象方法,导致已经使用Lambda表达式的地方出错。@FunctionalInterface устраняет возможные ошибки на уровне компиляции.

Например, когда вы аннотируете @FunctionalInterface и пишете два абстрактных метода в интерфейсе, появится следующее приглашение:

Multiple non-overriding abstract methods found in interface com.secbro2.lambda.NoParamInterface

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

此处的方法限制为抽象方法,如果接口内有其他静态方法则不会受限制。


Ссылка на метод, операция с двойным двоеточием

Формат [ссылки на метод]: classname::methodname

Такие выражения, как ClassName::methodName или objectName::methodName, мы называем это ссылкой на метод, обычно используемой в лямбда-выражениях.

// 无参数情况
NoParamInterface paramInterface2 = ()-> new HashMap<>();
// 可替换为
NoParamInterface paramInterface1 = HashMap::new;

// 一个参数情况
OneParamInterface oneParamInterface1 = (String string) -> System.out.print(string);
// 可替换为
OneParamInterface oneParamInterface2 = (System.out::println);

// 两个参数情况
Comparator c = (Computer c1, Computer c2) -> c1.getAge().compareTo(c2.getAge());
// 可替换为
Comparator c = (c1, c2) -> c1.getAge().compareTo(c2.getAge());
// 进一步可替换为
Comparator c = Comparator.comparing(Computer::getAge);

В другом примере мы используем функциональный интерфейс java.util.function.Function для реализации функции String to Integer, которую можно записать следующим образом:

Function<String, Integer> function = Integer::parseInt;
Integer num = function.apply("1");

Согласно определению интерфейса Function, Function, где T представляет входящий тип, а R представляет возвращаемый тип. В частности, реализован метод применения функции, и в ее методе вызывается метод Integer.parseInt.

Благодаря приведенному выше объяснению базовая грамматика была завершена. Следующее содержимое демонстрирует, как использовать его в различных сценариях один за другим на примерах.


Пример инициализации исполняемого потока

Инициализация исполняемого потока — типичный сценарий приложения.

// 匿名函类写法
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("小白也能看懂了的Lambda 表达式");
    }
}).start();

// lambda表达式写法
new Thread(() -> System.out.println("小白也能看懂了的Lambda 表达式")).start();

// lambda表达式 如果方法体内有多行代码需要带大括号
new Thread(() -> {
    System.out.println("小白也能看懂了的Lambda 表达式");
    System.out.println("小白也能看懂了的Lambda 表达式");
}).start();

通常都会把lambda表达式内部变量的名字起得短一些,这样能使代码更简短。


пример обработки событий

Прослушиватели событий часто используются в программировании Swing API.

// 匿名函类写法
JButton follow =  new JButton("关注");
follow.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("小白也能看懂了的Lambda 表达式");
    }
});

// lambda表达式写法
follow.addActionListener((e) -> System.out.println("小白也能看懂了的Lambda 表达式"));

// lambda表达式写法
follow.addActionListener((e) -> {
    System.out.println("小白也能看懂了的Lambda 表达式");
    System.out.println("小白也能看懂了的Lambda 表达式");
});

Пример вывода обхода списка

Традиционно для обхода списка используется цикл for.После Java 8 в List появился метод forEach, который можно комбинировать с лямбда-выражениями для написания более лаконичных методов.

List<String> list = Arrays.asList("小白","表达式","Lambda");
// 传统遍历
for(String str : list){
    System.out.println(str);
}
// lambda表达式写法
list.forEach(str -> System.out.println(str));
// lambda表达式写法
list.forEach(System.out::println);

Пример функционального интерфейса

В приведенном выше примере вы видели использование функционального интерфейса java.util.function.Function, и в пакете java.util.function есть другие классы, поддерживающие функциональное программирование на Java. Например, с помощью функционального интерфейса Predicate и лямбда-выражений вы можете добавить логику к методам API для поддержки более динамичного поведения с меньшим количеством кода.

@Test
public void testPredicate() {

    List<String> list = Arrays.asList("小白", "表达式", "Lambda");

    filter(list, (str) -> ("表达式".equals(str)));

    filter(list, (str) -> (((String) str).length() == 5));

}

public static void filter(List<String> list, Predicate condition) {
    for (String content : list) {
        if (condition.test(content)) {
            System.out.println("符合条件的内容:" + content);
        }
    }
}

Запись в методе фильтра может быть дополнительно упрощена:

list.stream().filter((content) -> condition.test(content)).forEach((content) ->System.out.println("符合条件的内容:" + content));

list.stream().filter(condition::test).forEach((content) ->System.out.println("符合条件的内容:" + content));

list.stream().filter(condition).forEach((content) ->System.out.println("符合条件的内容:" + content));

Если вам не нужна конкатенация строки «квалифицированный контент:», ее можно еще больше упростить:

list.stream().filter(condition).forEach(System.out::println);

Если условия оценки для вызова метода фильтра также написаны вместе, содержимое тестового метода может быть реализовано одной строкой кода:

list.stream().filter((str) -> ("小白也能看懂了的Lambda 表达式".equals(str))).forEach(System.out::println);

Если два условия должны быть выполнены одновременно или одно из них должно быть выполнено, Predicate может объединить такие несколько условий в одно.

Predicate start = (str) -> (((String) str).startsWith("程序"));
Predicate len = (str) -> (((String) str).length() == 5);

list.stream().filter(start.and(len)).forEach(System.out::println);

Примеры, связанные с потоком

существует"Подробное объяснение и реальная борьба с новыми функциями Java8 STREAM"Использование Stream было объяснено в этой статье. Считаете ли вы, что использование Stream неотделимо от лямбда-выражений. Да, все операции Stream должны принимать лямбда-выражения в качестве параметров.

В качестве примера возьмем метод карты Stream:

Stream.of("a","b","c").map(item -> item.toUpperCase()).forEach(System.out::println);
Stream.of("a","b","c").map(String::toUpperCase).forEach(System.out::println);

Разница между лямбда-выражением и анонимным классом

  • Разница между ключевыми словами: для анонимных классов ключевое слово this указывает на анонимный класс, в то время как для лямбда-выражений ключевое слово this указывает на внешний класс класса, который окружает лямбда-выражение, то есть значение использования this вне выражение то же самое.
  • Метод компиляции: когда компилятор Java компилирует лямбда-выражение, он преобразует его в закрытый метод класса, а затем динамически связывает его и вызывает с помощью инструкции invokedynamic. Анонимный внутренний класс по-прежнему является классом, и компилятор автоматически назовет класс и создаст файл класса при компиляции. Первый из них берет в качестве примера исходный код класса ServletWebServerApplicationContext в Spring Boot:
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
   return this::selfInitialize;
}

private void selfInitialize(ServletContext servletContext) throws ServletException {
   prepareWebApplicationContext(servletContext);
   registerApplicationScope(servletContext);
   WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(),servletContext);
   for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
      beans.onStartup(servletContext);
   }
}

Среди них это здесь указывает на класс, в котором находится метод getSelfInitializer.


резюме

До сих пор было объяснено основное использование лямбда-выражений Java8, и самое главное — усердно практиковаться, чтобы добиться идеального использования практики. Конечно, вначале может быть адаптационный период, в течение которого этот сборник статей можно использовать как пособие для справки. Оригинальная ссылка:«Подробное руководство по лямбда-выражениям Java8 и примеры»