Зачем вводить лямбда-выражения
Что бы вы сделали до 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 и примеры»