English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Широкий анализ новых возможностей JDK8 с помощью Lambda-выражений

Первый раз я встретил выражения Lambda в TypeScript (надстройке JavaScript), и это было для того, чтобы использовать метод this за пределами текущего метода. После использования я внезапно подумал, что это не новая функциональность JDK8? Поэтому я решил ознакомиться с соответствующими материалами и записать их:

1. Параметризация поведения

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

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

public class Apple {
/** Номер */
private long id;
/** Цвет */
private Color color;
/** Веса */
private float weight;
/** Местоположение */
private String origin;
public Apple() {
}
public Apple(long id, Color color, float weight, String origin) {
this.id = id;
this.color = color;
this.weight = weight;
this.origin = origin;
}
// Пропущены getter и setter
}

Пользовательские требования на начальном этапе могут быть очень простыми, например, просто希望能够 через программу фильтровать зеленые яблоки, и мы можем быстро реализовать это через программу:

public static List<Apple> filterGreenApples(List<Apple> apples) {
List<Apple> filterApples = new ArrayList<>();
for (final Apple apple : apples) {
if (Color.GREEN.equals(apple.getColor())) {
filterApples.add(apple);
}
}
return filterApples;
}

Этот код прост, нечего особенно отметить. Но если потребность пользователя изменится на зеленый, кажется, что изменение кода тоже просто, просто нужно изменить условие проверки с зеленого на красный. Но мы должны учитывать другую проблему, что если условия будут часто изменяться, как быть? Если это только изменение цвета, то хорошо, пусть пользователь передаст условия цвета в качестве параметра, параметром метода будет «сборка, которую нужно проверить, и цвет для фильтрации». Но если пользователь хочет не только проверять цвет, но и вес, размер и т.д., что делать? Не кажется ли вам, что можно просто добавить разные параметры для выполнения проверки? Но действительно ли это правильный способ передачи параметров, если условия фильтрации становятся все более сложными и комбинационные模式的 все более сложными? В этом случае мы можем параметризовать поведение, выделив условия фильтрации и передавая их в качестве параметра, в этом случае мы можем создать интерфейс для проверки:

public interface AppleFilter {
/**
* Абстрактные условия фильтрации
*
* @param apple
* @return
*/
boolean accept(Apple apple);
}
/**
* Оболочка условий фильтрации в интерфейс
*
* @param apples
* @param filter
* @return
*/
public static List<Apple> filterApplesByAppleFilter(List<Apple> apples, AppleFilter filter) {
List<Apple> filterApples = new ArrayList<>();
for (final Apple apple : apples) {
if (filter.accept(apple)) {
filterApples.add(apple);
}
}
return filterApples;
}

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

public static void main(String[] args) {
List<Apple> apples = new ArrayList<>();
// Фильтрация яблок
List<Apple> filterApples = filterApplesByAppleFilter(apples, new AppleFilter() {
@Override
public boolean accept(Apple apple) {
// Сортировать красные яблоки весом более 100г
return Color.RED.equals(apple.getColor()) && apple.getWeight() > 100;
}
});
}

Этот дизайн часто используется в JDK, например, Java.util.Comparator, java.util.concurrent.Callable и т.д. При использовании таких интерфейсов мы можем определить конкретное логическое выполнение функции с помощью анонимного класса в месте конкретного вызова, но, как видно из кода, это очень гик, но не очень лаконично. В Java 8 мы можем упростить это с помощью lambda:

// Фильтрация яблок
List<Apple> filterApples = filterApplesByAppleFilter(apples,
(Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100);
//()->xxx Внутри () находится список параметров метода, xxx - это реализация метода

Второй раздел. Определение выражения lambda

Мы можем определить выражение lambda как компактную, передаваемую анонимную функцию. Сначала我们需要 понять, что выражение lambda по своей сути является функцией, хотя оно не принадлежит к какой-либо конкретной классе, но обладает списком параметров, телом функции, типом возвращаемого значения и способностью выбрасывать исключения;其次, оно анонимное, выражение lambda не имеет конкретного имени функции. Выражение lambda можно передавать как параметр, что значительно упрощает написание кода. Формат определения следующий:

Формат один: список параметров -> выражение

Формат два: список параметров -> {сбор выражений}

Стоит отметить, что выражение lambda подразумевает ключевое слово return, поэтому в одном выражении мы не должны явно писать ключевое слово return, но когда выражение является集合ом предложений,则需要 явно добавить return и обернуть его фигурными скобками { }, вот несколько примеров:

// Возвращает длину заданного строки, подразумевается ключевое слово return
(String s) -> s.length() 
// Метод без параметров, который всегда возвращает 42
() -> 42 
// Если выражение включает несколько строк, то его нужно обернуть в фигурные скобки
(int x, int y) -> {
int z = x * y;
return x + z;
}

Третий раздел. Использование выражений lambda с функциональными интерфейсами

Использование выражений lambda требует наличия функционального интерфейса, то есть только в местах, где есть функциональный интерфейс, мы можем его упростить с помощью выражений lambda.

Создание пользовательских функциональных интерфейсов:

Функциональный интерфейс определен как интерфейс, обладающий только одним абстрактным методом. В Java 8 улучшение в определении интерфейса заключается в введении методов по умолчанию, что позволяет нам предоставлять методам по умолчанию реализацию в интерфейсе. Но неважно, сколько методов по умолчанию существует, если есть только один и только один абстрактный метод, то это функциональный интерфейс, как показано ниже (ссылка на上面的 AppleFilter):

/**
* Интерфейс фильтрации яблок
*/
@FunctionalInterface
public interface AppleFilter {
/**
* Абстрактные условия фильтрации
*
* @param apple
* @return
*/
boolean accept(Apple apple);
}

AppleFilter содержит только один абстрактный метод accept(Apple apple), по определению его можно рассматривать как функциональный интерфейс. При определении мы добавили аннотацию @FunctionalInterface, чтобы отметить, что этот интерфейс является функциональным интерфейсом. Хотя это и не обязательно, добавление этой аннотации ограничивает интерфейс только одним абстрактным методом, в противном случае будет выведена ошибка. Поэтому рекомендуется добавлять аннотацию для функциональных интерфейсов.

Функциональные интерфейсы, входящие в jdk:

jdk уже встроил в lambda-выражения множество функциональных интерфейсов, далее мы рассмотрим примеры использования Predicate<T>, Consumer<T> и Function<T, R>.

Predicate:

@FunctionalInterface
public interface Predicate<T> {
/**
* Оценивает этот предикат по данному аргументу
*
/* @param t входной аргумент */
* @return {@code true}, если входной аргумент соответствует предикату
* в противном случае {@code false}
*/
boolean test(T t);
}

Функция Predicate подобна上面的 AppleFilter, использует условия, установленные снаружи, для проверки传入ых параметров и возвращает результат проверки boolean, далее с помощью Predicate фильтруется элемент списка:

/**
*
/* @param list */
* @param predicate
/* @param <T> */
* @return
*/
public <T> List<T> filter(List<T> list, Predicate<T> predicate) {
List<T> newList = new ArrayList<T>();
for (final T t : list) {
if (predicate.test(t)) {
newList.add(t);
}
}
return newList;
}

Использование:

demo.filter(list, (String str) -> null != str && !str.isEmpty());

Consumer

@FunctionalInterface
public interface Consumer<T> {
/**
/* Выполняет эту операцию на данном аргументе. */
*
/* @param t входной аргумент */
*/
void accept(T t);
}

Consumer предоставляет абстрактную функцию accept, которая принимает параметр, но не возвращает значение, ниже используется Consumer для прогона набора.

/**
/* Пройдите через набор, выполняя пользовательское поведение */
*
/* @param list */
/* @param consumer */
/* @param <T> */
*/
public <T> void filter(List<T> list, Consumer<T> consumer) {
for (final T t : list) {
consumer.accept(t);
}
}

Используя上面的 функциональный интерфейс, пройдите через набор строк и напечатайте пустые строки:

demo.filter(list, (String str) -> {
if (StringUtils.isNotBlank(str)) {
System.out.println(str);
}
});

Function

@FunctionalInterface
public interface Function<T, R> {
/**
* Применяет эту функцию к данному аргументу.
*
/* @param t аргумент функции */
/* @return результат функции */
*/
R apply(T t);
}

Funcation выполняет преобразовательные операции, входные данные имеют тип T, возвращаемые данные имеют тип R, ниже используется Function для преобразования набора:

public <T, R> List<R> filter(List<T> list, Function<T, R> function) {
List<R> newList = new ArrayList<R>();
for (final T t : list) {
newList.add(function.apply(t));
}
return newList;
}

Другое:

demo.filter(list, (String str) -> Integer.parseInt(str));

Эти функциональные интерфейсы также предоставляют некоторыеdefault реализации логических операций, о которых поговорим позже, когда будем обсуждать default методы интерфейсов Java 8.

В процессе использования необходимо учитывать некоторые моменты:

Типовая инференция:

Во время программирования, иногда может быть трудно понять, какая функциональная интерфейс будет использоваться в нашем коде вызова, на самом деле компилятор делает правильный вывод на основе параметров, типа возвращаемого значения, типа исключений (если они существуют)
Во время вызова, в некоторых случаях можно пропустить тип параметра, чтобы еще больше упростить код:

// Фильтрация яблок
List<Apple> filterApples = filterApplesByAppleFilter(apples,
(Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100);
// В некоторых случаях мы даже можем пропустить тип параметра, компилятор правильно определяет тип по контексту
List<Apple> filterApples = filterApplesByAppleFilter(apples,
apple -> Color.R
ED.equals(apple.getColor()) && apple.getWeight() >= 100);

Локальные переменные

В всех примерах выше наши lambda-выражения используют их параметры, но мы также можем использовать локальные переменные в lambda, например:

int weight = 100;
List<Apple> filterApples = filterApplesByAppleFilter(apples,
apple -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= weight);

В этом примере мы используем локальную переменную weight в lambda, но использование локальных переменных в lambda требует, чтобы переменная была явно объявлена как final или фактический final, это主要是因为 локальные переменные хранятся на стеке, а lambda-выражения выполняются в другом потоке, и когда этот поток пытается доступ к локальной переменной, переменная может быть изменена или回收лена, поэтому использование final решает проблему безопасности потоков.

Четвертый. Метод ссылки

Использование метода ссылки может еще больше упростить код, иногда это упрощение делает код более наглядным, рассмотрим пример:

/* ... пропущена инициализация apples */
// Использование lambda-выражения
apples.sort((Apple a, Apple b) -> Float.compare(a.getWeight(), b.getWeight()));
// Использование метода ссылок
apples.sort(Comparator.comparing(Apple::getWeight));

Методы ссылок через :: соединяют метод и сам метод, и они делятся на три основные категории:

статический метод

(args) -> ClassName.staticMethod(args)

преобразуется в

ClassName::staticMethod

метод экземпляра параметра

(args) -> args.instanceMethod()

преобразуется в

ClassName::instanceMethod // ClassName является типом args

внешний метод экземпляра

(args) -> ext.instanceMethod(args)

преобразуется в

ext::instanceMethod(args)

См. также:

http://www.codeceo.com/article/lambda-of-java-8.html

Названные выше是新版本JDK8的Lambda выражение, которое я хочу представить вам. Надеюсь, это поможет вам. Если у вас есть какие-либо вопросы, пожалуйста, оставьте комментарий, и я отвечу вам своевременно. Также очень благодарю всех за поддержку сайта呐喊教程!

Заявление: Контент этой статьи взят из Интернета, авторские права принадлежат соответствующему автору. Контент предоставлен пользователями Интернета, сайт не обладает правами собственности, не был отредактирован вручную и не несет ответственности за него. Если вы обнаружите контент,涉嫌侵犯版权, пожалуйста, отправьте письмо по адресу: notice#oldtoolbag.com (во время отправки письма замените # на @) для сообщения о нарушении и предоставьте соответствующие доказательства. При обнаружении факта нарушения, сайт незамедлительно удалят涉嫌侵权的内容。

Основной учебник
Рекомендуем