Java8 Lambda Expression Подробное руководство и примеры

Java

Сначала о тревоге, Java8 вышла 18 марта 2014. Прошло уже почти 6 лет.Если вы не применяли новые возможности Java8, или вообще ничего о ней не знаете, то вам действительно нужно платить внимание на публичный аккаунт «Program New «Vision», хорошая серия для знакомства с новыми возможностями Java8. Лямбда-выражения уже широко используются в новом фреймворке, если вы ничего не знаете о лямбда-выражениях, вам действительно стоит изучить эту статью.

Теперь давайте перейдем к теме Lambda Java8, сначала посмотрим на выражение произношения ([ˈlæmdə]). Обратите внимание на произношение слова, b молчит, da произносится [də].

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

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

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

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("欢迎关注公众号:程序新视界");
    }
}).start();

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

new Thread(() -> System.out.println("欢迎关注公众号:程序新视界")).start();

Разве это не так просто!

Все мы знаем, что Java — это объектно-ориентированный язык программирования, и все, за исключением некоторых простых типов данных, является объектами. Поэтому определение функций или методов в Java неотделимо от объектов, а это означает, что методы или функции напрямую передавать в качестве параметров сложно, и появление лямбда-выражений в Java8 решает эту проблему.

Лямбда-выражения делают Java способной к функциональному программированию, но в Java лямбда-выражения — это объекты, которые должны быть присоединены к объекту особого типа — функциональному интерфейсу, который будет подробно объяснен позже.

Введение в лямбда-выражения

Лямбда-выражение — это анонимная функция (что не совсем точно для Java), а с точки зрения непрофессионала — это метод без объявления, то есть метод без модификаторов доступа, объявления возвращаемого значения и имени. Очевидным преимуществом использования лямбда-выражений является то, что код можно сделать более лаконичным и компактным.

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

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

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

//没有参数
() -> body

// 1个参数
(param) -> body
// 或
(param) ->{ body; }

// 多个参数
(param1, param2...) -> { body }
// 或
(type1 param1, type2 param2...) -> { body }

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

// 无参数,返回值为字符串“公众号:程序新视界”
() -> "公众号:程序新视界";

// 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. Конечно, если интерфейс определяет только один абстрактный метод, лямбда-выражения можно использовать и без этой аннотации, но без ограничений этой аннотации позже могут быть добавлены другие абстрактные методы, что приведет к ошибкам там, где использовались лямбда-выражения. Возможные ошибки устраняются на уровне компиляции с помощью @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("欢迎关注公众号:程序新视界");
    }
}).start();

// lambda表达式写法
new Thread(() -> System.out.println("欢迎关注公众号:程序新视界")).start();

// lambda表达式 如果方法体内有多行代码需要带大括号
new Thread(() -> {
    System.out.println("欢迎关注公众号");
    System.out.println("程序新视界");
}).start();

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

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

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

// 匿名函类写法
JButton follow =  new JButton("关注");
follow.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("已关注公众号:程序新视界");
    }
});

// lambda表达式写法
follow.addActionListener((e) -> System.out.println("已关注公众号:程序新视界"));

// lambda表达式写法
follow.addActionListener((e) -> {
    System.out.println("已关注公众号");
    System.out.println("程序新视界");
});

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

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

List<String> list = Arrays.asList("欢迎","关注","程序新视界");

// 传统遍历
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("欢迎", "关注", "程序新视界");

    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) -> ("程序新视界".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);

Дополнительные примеры использования см. в разделе Stream "Подробное объяснение и реальная борьба с новыми функциями JAVA8 STREAM" статья.

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

  • Разница между ключевыми словами: для анонимных классов ключевое слово 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 Lambda Expression Подробное руководство и примеры


Программа Новые Горизонты: Захватывающие и растущие нельзя пропустить

csdn-微信公众号