Сначала о тревоге, 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 Подробное руководство и примеры》