Введение
Лямбда-выражения — важная новая функция в Java SE 8. Лямбда-выражения позволяют заменять функциональные интерфейсы выражениями. Лямбда-выражение похоже на метод: оно предоставляет обычный список параметров и тело (которое может быть выражением или блоком кода), которое использует эти параметры.
Лямбда-выражения также расширяют библиотеку коллекций. В Java SE 8 добавлены два пакета для пакетных операций над данными сбора: пакет java.util.function и пакет java.util.stream. Поток похож на итератор, но имеет множество дополнительных функций. В целом, лямбда-выражения и потоки представляют собой самые большие изменения с момента добавления дженериков и аннотаций в язык Java. В этой статье мы увидим возможности лямбда-выражений и потоков на примере простых и сложных примеров.
Экологическая подготовка
Если Java 8 не установлена, вам следует сначала установить ее, прежде чем вы сможете использовать лямбду и поток (переводчик рекомендует установить ее на виртуальную машину для тестирования). Инструменты и IDE, такие как NetBeans и IntelliJ IDEA, поддерживают функции Java 8, включая лямбда-выражения, повторяемые аннотации, компактные профили и другие функции.
Синтаксис лямбда-выражения
Основной синтаксис:
(параметры) -> выражение
или
(параметры) ->{ утверждения }
Вот простой пример лямбда-выражения Java:
Скопируйте код кода следующим образом:
// 1. Параметры не требуются, возвращаемое значение — 5
() -> 5
// 2. Получаем параметр (числовой тип) и возвращаем удвоенное его значение
х -> 2*х
// 3. Принимаем 2 параметра (числа) и возвращаем их разницу
(х, у) -> ху
// 4. Получаем 2 целых числа типа int и возвращаем их сумму
(int x, int y) -> x + y
// 5. Примем строковый объект и выведем его на консоль, не возвращая никакого значения (похоже, что он возвращает void)
(Строка s) -> System.out.print(s)
Базовый пример лямбды
Теперь, когда мы знаем, что такое лямбда-выражения, давайте начнем с нескольких простых примеров. В этом разделе мы увидим, как лямбда-выражения влияют на способ написания кода. Предположим, что существует список игроков, программист может использовать для перемещения оператор for («цикл for»), который можно преобразовать в другую форму в Java SE 8:
Скопируйте код кода следующим образом:
String[] atp = {"Рафаэль Надаль", "Новак Джокович",
«Станислас Вавринка»,
«Дэвид Феррер», «Роджер Федерер»,
«Энди Мюррей», «Томас Бердых»,
«Хуан Мартин Дель Потро»};
List<String> player = Arrays.asList(atp);
//Предыдущий метод цикла
for (Строковый игрок: игроки) {
System.out.print(player + "; ");
}
//Используем лямбда-выражения и функциональные операции
playerers.forEach((player) -> System.out.print(player + "; "));
//Используем оператор двойного двоеточия в Java 8
player.forEach(System.out::println);
Как видите, лямбда-выражения позволяют сократить наш код до одной строки. Другой пример — программы с графическим пользовательским интерфейсом, где анонимные классы можно заменить лямбда-выражениями. Точно так же его можно использовать и при реализации интерфейса Runnable:
Скопируйте код кода следующим образом:
//Используем анонимный внутренний класс
btn.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle (событие ActionEvent) {
System.out.println("Привет, мир!");
}
});
// Или используйте лямбда-выражение
btn.setOnAction(event -> System.out.println("Hello World!"));
Ниже приведен пример использования лямбда-выражений для реализации интерфейса Runnable:
Скопируйте код кода следующим образом:
// 1.1 Использование анонимных внутренних классов
новый поток (новый Runnable () {
@Override
общественный недействительный запуск () {
System.out.println("Привет, мир!");
}
}).начинать();
// 1.2 Использование лямбда-выражения
new Thread(() -> System.out.println("Привет, мир!")).start();
// 2.1 Использование анонимных внутренних классов
Runnable Race1 = новый Runnable() {
@Override
общественный недействительный запуск () {
System.out.println("Привет, мир!");
}
};
// 2.2 Использование лямбда-выражения
Runnable Race2 = () -> System.out.println("Привет, мир!");
// Вызов метода run напрямую (новый поток не открывается!)
гонка1.запуск();
гонка2.run();
Лямбда-выражение Runnable использует блочный формат для преобразования пяти строк кода в однострочный оператор. Далее, в следующем разделе мы будем использовать лямбды для сортировки коллекции.
Сортировка коллекций с помощью Lambdas
В Java класс Comparator используется для сортировки коллекций. В приведенном ниже примере мы будем основываться на имени, фамилии, длине имени и последней букве игрока. Как и в предыдущем примере, мы сначала используем анонимный внутренний класс для сортировки, а затем используем лямбда-выражения для оптимизации нашего кода.
В первом примере мы отсортируем список по имени. Если использовать старый способ, код будет выглядеть так:
Скопируйте код кода следующим образом:
String[] player = {"Рафаэль Надаль", "Новак Джокович",
«Станислас Вавринка», «Дэвид Феррер»,
«Роджер Федерер», «Энди Мюррей»,
«Томас Бердых», «Хуан Мартин Дель Потро»,
«Ришар Гаске», «Джон Иснер»};
// 1.1 Использование анонимных внутренних классов для сортировки игроков по имени
Arrays.sort(players, new Comparator<String>() {
@Override
публичное сравнение int (String s1, String s2) {
возврат (s1.compareTo(s2));
}
});
Используя лямбды, той же функциональности можно добиться с помощью следующего кода:
Скопируйте код кода следующим образом:
// 1.2 Использование лямбда-выражения для сортировки игроков
Comparator<String> sortByName = (String s1, String s2) -> (s1.compareTo(s2));
Arrays.sort(игроки, sortByName);
// 1.3 также может иметь следующий вид:
Arrays.sort(игроки, (String s1, String s2) -> (s1.compareTo(s2)));
Другие рейтинги выглядят следующим образом. Как и в приведенном выше примере, код реализует Comparator через анонимные внутренние классы и некоторые лямбда-выражения:
Скопируйте код кода следующим образом:
// 1.1 Использование анонимных внутренних классов для сортировки игроков по фамилии
Arrays.sort(players, new Comparator<String>() {
@Override
публичное сравнение int (String s1, String s2) {
return (s1.substring(s1.indexOf(" ")).compareTo(s2.substring(s2.indexOf(" "))));
}
});
// 1.2 Использование лямбда-выражения для сортировки по фамилии
Comparator<String> sortBySurname = (String s1, String s2) ->
( s1.substring(s1.indexOf(" ")).compareTo( s2.substring(s2.indexOf(" ")) ) );
Arrays.sort(игроки, sortByFurname);
// 1.3 Или вот так, интересно, автор оригинала ошибся, там столько скобок...
Arrays.sort(игроки, (Строка s1, Строка s2) ->
( s1.substring(s1.indexOf(" ")).compareTo( s2.substring(s2.indexOf(" ")) ) )
);
// 2.1 Использование анонимных внутренних классов для сортировки игроков по длине имени
Arrays.sort(players, new Comparator<String>() {
@Override
публичное сравнение int (String s1, String s2) {
return (s1.length() - s2.length());
}
});
// 2.2 Использование лямбда-выражения для сортировки по длине имени
Comparator<String> sortByNameLenght = (String s1, String s2) -> (s1.length() - s2.length());
Arrays.sort(игроки, sortByNameLenght);
// 2.3 или это
Arrays.sort(игроки, (String s1, String s2) -> (s1.length() - s2.length()));
// 3.1 Использование анонимного внутреннего класса для сортировки игроков по последней букве
Arrays.sort(players, new Comparator<String>() {
@Override
публичное сравнение int (String s1, String s2) {
return (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1));
}
});
// 3.2 Использование лямбда-выражения для сортировки по последней букве
Компаратор<String> sortByLastLetter =
(Строка s1, Строка s2) ->
(s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1));
Arrays.sort(игроки, sortByLastLetter);
// 3.3 или это
Arrays.sort(players, (String s1, String s2) -> (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1)));
Вот и все, просто и интуитивно понятно. В следующем разделе мы рассмотрим дополнительные возможности лямбда-выражений и будем использовать их с потоками.
Использование лямбд и потоков
Stream — это оболочка для коллекций и обычно используется вместе с лямбдой. Использование лямбда-выражений может поддерживать множество операций, таких как отображение, фильтрация, ограничение, сортировка, подсчет, минимум, максимум, сумма, сбор и т. д. Аналогичным образом, потоки используют ленивые операции, они фактически не считывают все данные, а синтаксис цепочки завершается при обнаружении таких методов, как getFirst(). В следующих примерах мы рассмотрим, что могут делать лямбды и потоки. Мы создали класс Person и использовали этот класс для добавления в список некоторых данных, которые будут использоваться для дальнейших операций потоковой передачи. Person — это простой класс POJO:
Скопируйте код кода следующим образом:
общественный класс Person {
частная строка имя, фамилия, должность, пол;
частная внутренняя зарплата, возраст;
public Person(String firstName, String LastName, String job,
Строка пол, int возраст, int зарплата) {
это.ПервоеИмя = ПервоеИмя;
this.lastName = последнее имя;
this.gender = пол;
this.age = возраст;
this.job = работа;
this.salary = зарплата;
}
// Геттер и сеттер
// .
}
Далее мы создадим два списка, оба из которых будут использоваться для хранения объектов Person:
Скопируйте код кода следующим образом:
List<Person> javaProgrammers = new ArrayList<Person>() {
{
add(new Person("Элсдон", "Джейкоб", "Java-программист", "мужчина", 43, 2000));
add(new Person("Тамсен", "Бриттани", "Java-программист", "женщина", 23, 1500));
add(new Person("Флойд", "Донни", "Java-программист", "мужчина", 33, 1800));
add(new Person("Синди", "Джони", "Java-программист", "женщина", 32, 1600));
add(new Person("Вере", "Херви", "Java-программист", "мужчина", 22, 1200));
add(new Person("Мод", "Джейми", "Java-программист", "женщина", 27, 1900));
add(new Person("Шон", "Рэндалл", "Java-программист", "мужчина", 30, 2300));
add(new Person("Джейден", "Коррина", "Java-программист", "женщина", 35, 1700));
add(new Person("Палмер", "Ден", "Java-программист", "мужчина", 33, 2000));
add(new Person("Аддисон", "Пэм", "Java-программист", "женщина", 34, 1300));
}
};
List<Person> phpProgrammers = new ArrayList<Person>() {
{
add(new Person("Jarrod", "Pace", "PHP-программист", "мужчина", 34, 1550));
add(new Person("Кларетта", "Сисели", "Программист PHP", "женщина", 23, 1200));
add(new Person("Виктор", "Ченнинг", "PHP-программист", "мужчина", 32, 1600));
add(new Person("Тори", "Шерил", "PHP-программист", "женщина", 21, 1000));
add(new Person("Осборн", "Шад", "PHP-программист", "мужчина", 32, 1100));
add(new Person("Розалинда", "Лейла", "PHP-программист", "женщина", 25, 1300));
add(new Person("Фрейзер", "Хьюи", "Программист PHP", "мужчина", 36, 1100));
add(new Person("Куинн", "Тамара", "PHP-программист", "женщина", 21, 1000));
add(new Person("Элвин", "Лэнс", "PHP-программист", "мужчина", 38, 1600));
add(new Person("Эвонн", "Шари", "Программист PHP", "женщина", 40, 1800));
}
};
Теперь мы используем метод forEach для перебора и вывода приведенного выше списка:
Скопируйте код кода следующим образом:
System.out.println("Имена всех программистов:");
javaProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
phpProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
Также мы используем метод forEach для увеличения зарплаты программиста на 5%:
Скопируйте код кода следующим образом:
System.out.println("Повысить зарплату программистам на 5%:");
Consumer<Person> GiveRaise = e -> e.setSalary(e.getSalary() / 100 * 5 + e.getSalary());
javaProgrammers.forEach(giveRaise);
phpProgrammers.forEach(giveRaise);
Еще один полезный метод — filter(), который позволяет нам отображать PHP-программистов с месячной зарплатой более 1400 долларов:
Скопируйте код кода следующим образом:
System.out.println("Вот PHP-программисты с месячной зарплатой более 1400 долларов:")
phpProgrammers.stream()
.filter((p) -> (p.getSalary() > 1400))
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
Мы также можем определить фильтры, а затем повторно использовать их для выполнения других операций:
Скопируйте код кода следующим образом:
// определяем фильтры
Predicate<Person> ageFilter = (p) -> (p.getAge() > 25);
Predicate<Person> SalaryFilter = (p) -> (p.getSalary() > 1400);
Predicate<Person> гендерный фильтр = (p) -> («женский».equals(p.getGender()));
System.out.println("Здесь представлены женщины-программисты PHP старше 24 лет с ежемесячной зарплатой более 1400 долларов США:");
phpProgrammers.stream()
.filter(ageFilter)
.filter(salaryFilter)
.filter(полФильтер)
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
//Повторное использование фильтров
System.out.println("Женщины-программисты Java старше 24 лет:");
javaProgrammers.stream()
.filter(ageFilter)
.filter(полФильтер)
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
Используйте метод limit, чтобы ограничить количество наборов результатов:
Скопируйте код кода следующим образом:
System.out.println("Первые три Java-программиста:");
javaProgrammers.stream()
.предел(3)
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
System.out.println("Три лучшие женщины-программисты на Java:");
javaProgrammers.stream()
.filter(полФильтер)
.предел(3)
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
А как насчет сортировки? Можем ли мы справиться с этим в потоке? Ответ — да. В следующем примере мы отсортируем Java-программистов по имени и зарплате, поместим их в список, а затем отобразим этот список:
Скопируйте код кода следующим образом:
System.out.println("Сортировать по имени и отобразить 5 лучших Java-программистов:");
List<Person> sortedJavaProgrammers = javaProgrammers
.транслировать()
.sorted((p, p2) -> (p.getFirstName().compareTo(p2.getFirstName())))
.предел(5)
.collect(toList());
sortedJavaProgrammers.forEach((p) -> System.out.printf("%s %s; %n", p.getFirstName(), p.getLastName()));
System.out.println("Сортировать Java-программистов по зарплате:");
sortedJavaProgrammers = javaProgrammers
.транслировать()
.sorted( (p, p2) -> (p.getSalary() - p2.getSalary()) )
.collect( toList() );
sortedJavaProgrammers.forEach((p) -> System.out.printf("%s %s; %n", p.getFirstName(), p.getLastName()));
Если нас интересуют только самые низкие и самые высокие зарплаты, то быстрее, чем выбор первой/последней после сортировки, являются методы min и max:
Скопируйте код кода следующим образом:
System.out.println("Самый низкооплачиваемый Java-программист:");
Человек чел = JavaПрограммисты
.транслировать()
.min((p1, p2) -> (p1.getSalary() - p2.getSalary())))
.получать()
System.out.printf("Имя: %s %s; Зарплата: $%,d.", pers.getFirstName(), pers.getLastName(), pers.getSalary())
System.out.println("Java-программист с самой высокой зарплатой:");
Человек человек = JavaПрограммисты
.транслировать()
.max((p, p2) -> (p.getSalary() - p2.getSalary())))
.получать()
System.out.printf("Имя: %s %s; Зарплата: $%,d.", person.getFirstName(), person.getLastName(), person.getSalary())
В приведенном выше примере мы увидели, как работает метод сбора. В сочетании с методом карты мы можем использовать метод Collect, чтобы поместить наш набор результатов в String, Set или TreeSet:
Скопируйте код кода следующим образом:
System.out.println("Объедините имена PHP-программистов в строку:");
Строка phpDevelopers = phpProgrammers
.транслировать()
.map(Person::getFirstName)
.collect(joining(" ; ")); // Может использоваться как токен в дальнейших операциях.
System.out.println("Сохраните имена Java-программистов в Set:");
Set<String> javaDevFirstName = javaProgrammers
.транслировать()
.map(Person::getFirstName)
.collect(toSet());
System.out.println("Сохраните имена Java-программистов в TreeSet:");
TreeSet<String> javaDevLastName = javaProgrammers
.транслировать()
.map(Person::getLastName)
.collect(toCollection(TreeSet::new));
Потоки также могут быть параллельными. Примеры следующие:
Скопируйте код кода следующим образом:
System.out.println("Подсчитайте все деньги, выплаченные Java-программистам:");
int TotalSalary = JavaПрограммисты
.parallelStream()
.mapToInt(p -> p.getSalary())
.сум();
Мы можем использовать метод summaryStatistics для получения различных сводных данных элементов в потоке. Далее мы можем получить доступ к таким методам, как getMax, getMin, getSum или getAverage:
Скопируйте код кода следующим образом:
//Вычисление количества, минимума, максимума, суммы и среднего значения для чисел
Числа List<Integer> = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Статистика IntSummaryStatistics = числа
.транслировать()
.mapToInt((x) -> x)
.summaryStatistics();
System.out.println("Самое большое число в списке: " + stats.getMax());
System.out.println("Наименьшее число в списке: " + stats.getMin());
System.out.println("Сумма всех чисел: " + stats.getSum());
System.out.println("Среднее всех чисел: " + stats.getAverage());
Хорошо, вот и все, надеюсь, вам понравится!
Подвести итог
В этой статье мы изучили различные способы использования лямбда-выражений: от базовых примеров до более сложных примеров с использованием лямбда-выражений и потоков. Кроме того, мы также узнали, как использовать лямбда-выражения и класс Comparator для сортировки коллекций Java.
Примечание переводчика: хотя лямбда-выражение выглядит очень сложным, суть лямбда-выражения — это всего лишь «синтаксический сахар», который выводится компилятором и помогает преобразовать и обернуть его в обычный код, чтобы вы могли использовать меньше кода для достижения той же функции. . Я рекомендую не использовать его без разбора, потому что он похож на код, написанный некоторыми очень продвинутыми хакерами. Он краток, сложен для понимания и отладки, и обслуживающий персонал захочет вас отругать.