Аннотация перевода: карта (отображение) и сокращение (сокращение, упрощение) — два очень основных понятия в математике. Они уже давно появились в различных языках функционального программирования. Лишь в 2003 году Google продвинул их вперед. применил их После того, как параллельные вычисления были реализованы в распределенных системах, название этой комбинации стало сиять в компьютерном мире (любители функциональности, возможно, так не думают). В этой статье мы увидим дебют комбинаций карт и сокращений после того, как Java 8 поддерживает функциональное программирование (это всего лишь предварительное введение, о них будут специальные темы позже).
Уменьшить набор
На данный момент мы представили несколько новых методов работы с коллекциями: поиск совпадающих элементов, поиск отдельных элементов и преобразование коллекций. У этих операций есть одна общая черта: все они работают с одним элементом коллекции. Нет необходимости сравнивать элементы или выполнять операции над двумя элементами. В этом разделе мы рассмотрим, как сравнивать элементы и динамически поддерживать результат операции во время обхода коллекции.
Давайте начнем с простых примеров, а затем пойдем дальше. В первом примере мы сначала перебираем коллекцию друзей и вычисляем общее количество символов во всех именах.
Скопируйте код кода следующим образом:
System.out.println("Общее количество символов во всех именах: " + friends.stream()
.mapToInt(имя -> имя.длина())
.сум());
Чтобы подсчитать общее количество символов, нам нужно знать длину каждого имени. Это можно легко сделать с помощью метода mapToInt(). После того, как мы преобразовали имена в соответствующие длины, нам остается только сложить их в конце. Для этого у нас есть встроенный метод sum(). Вот окончательный результат:
Скопируйте код кода следующим образом:
Общее количество символов во всех именах: 26
Мы использовали вариант операции карты, метод mapToInt() (например, mapToInt, mapToDouble и т. д., который будет генерировать определенные типы потоков, например IntStream, DoubleStream), а затем вычисляли общее количество символов на основе возвращаемая длина.
Помимо метода sum, существует множество подобных методов, например max() для определения максимальной длины, min() для определения минимальной длины, sorted() для сортировки длин, Average() для определения длины. найти среднюю длину и т. д. подождите.
Еще одним привлекательным аспектом приведенного выше примера является набирающий популярность режим MapReduce. Метод map() выполняет сопоставление, а метод sum() является широко используемой операцией сокращения. Фактически, реализация метода sum() в JDK использует метод уменьшить(). Давайте взглянем на некоторые из наиболее часто используемых форм операций сокращения.
Например, мы перебираем все имена и печатаем имя с самым длинным именем. Если существует несколько самых длинных имен, выводим то, которое найдено первым. Один из способов заключается в том, что мы вычисляем максимальную длину, а затем выбираем первый элемент, соответствующий этой длине. Однако для этого требуется дважды пройти по списку, что слишком неэффективно. Здесь в игру вступает операция сокращения.
Мы можем использовать операцию сокращения для сравнения длин двух элементов, затем вернуть самый длинный из них и далее сравнить его с остальными элементами. Как и другие функции высшего порядка, которые мы видели ранее, метод сокращение() также обходит всю коллекцию. Помимо прочего, он записывает результат вычислений, возвращаемый лямбда-выражением. Если есть пример, который поможет нам лучше это понять, давайте сначала посмотрим на фрагмент кода.
Скопируйте код кода следующим образом:
Final Необязательный<String> aLongName = friends.stream()
.reduce((имя1, имя2) ->
имя1.длина() >= имя2.длина() ? имя1 : имя2);
aLongName.ifPresent(имя ->
System.out.println(String.format("Самое длинное имя: %s", name)));
Лямбда-выражение, передаваемое методу сокращение(), получает два параметра, имя1 и имя2, сравнивает их длину и возвращает самый длинный из них. Метод уменьшить() понятия не имеет, что мы собираемся делать. Эта логика вынесена в передаваемое нами лямбда-выражение — это облегченная реализация шаблона «Стратегия».
Это лямбда-выражение можно адаптировать к методу apply функционального интерфейса BinaryOperator в JDK. Это именно тот тип аргумента, который принимает метод сокращения. Давайте запустим этот метод сокращения и посмотрим, сможет ли он правильно выбрать первое из двух самых длинных имен.
Скопируйте код кода следующим образом:
Самое длинное имя: Брайан
Когда метод уменьшения() обходит коллекцию, он сначала вызывает лямбда-выражение для первых двух элементов коллекции, а результат, возвращаемый вызовом, продолжает использоваться для следующего вызова. Во втором вызове значение name1 привязано к результату предыдущего вызова, а значение name2 является третьим элементом коллекции. Остальные элементы также вызываются в этом порядке. Результатом последнего вызова лямбда-выражения является результат, возвращаемый всем методом уменьшить().
Метод сокращение() возвращает необязательное значение, поскольку переданная ему коллекция может быть пустой. В этом случае не было бы самого длинного имени. Если в списке есть только один элемент, метод уменьшения напрямую возвращает этот элемент и не вызывает лямбда-выражение.
Из этого примера мы можем сделать вывод, что результатом сокращения может быть не более одного элемента в наборе. Если мы хотим вернуть значение по умолчанию или базовое значение, мы можем использовать вариант метода уменьшения(), который принимает дополнительный параметр. Например, если самое короткое имя — Стив, мы можем передать его методу уменьшить() следующим образом:
Скопируйте код кода следующим образом:
конечная строка steveOrLonger = friends.stream()
.reduce("Стив", (имя1, имя2) ->
имя1.длина() >= имя2.длина() ? имя1 : имя2);
Если есть имя длиннее этого, то будет выбрано это имя, в противном случае будет возвращено базовое значение Steve; Эта версия метода сокращение() не возвращает необязательный объект, поскольку, если коллекция пуста, будет возвращено значение по умолчанию, независимо от случая, когда возвращаемое значение отсутствует.
Прежде чем закончить эту главу, давайте взглянем на очень простую, но не столь простую операцию в операциях над множествами: слияние элементов.
Объединить элементы
Мы научились находить элементы, перемещаться по коллекциям и преобразовывать их. Однако есть еще одна распространенная операция — объединение элементов коллекции — без этой недавно добавленной функции join() краткий и элегантный код, упомянутый ранее, был бы напрасным. Этот простой метод настолько практичен, что стал одной из наиболее часто используемых функций в JDK. Давайте посмотрим, как использовать его для печати элементов списка, разделенных запятыми.
Мы до сих пор пользуемся этим списком друзей. Если вы используете старый метод библиотеки JDK, что делать, если вы хотите распечатать все имена, разделенные запятыми?
Нам нужно перебирать список и печатать элементы один за другим. Цикл for в Java 5 улучшен по сравнению с предыдущим, поэтому давайте воспользуемся им.
Скопируйте код кода следующим образом:
for(Имя строки: друзья) {
System.out.print(имя + ", ");
}
Система.out.println();
Код очень простой, давайте посмотрим, что он выведет.
Скопируйте код кода следующим образом:
Брайан, Нейт, Нил, Раджу, Сара, Скотт,
Черт, эта раздражающая запятая в конце (можем ли мы винить в этом Скотта в конце?). Как я могу сказать Java не ставить здесь запятую? К сожалению, цикл выполняется шаг за шагом, и сделать что-то особенное в конце непросто. Чтобы решить эту проблему, мы можем использовать оригинальный метод цикла.
Скопируйте код кода следующим образом:
for(int i = 0; i < friends.size() - 1; i++) {
System.out.print(friends.get(i) + ", ");
}
если (friends.size() > 0)
System.out.println(friends.get(friends.size() - 1));
Посмотрим, в порядке ли вывод этой версии.
Скопируйте код кода следующим образом:
Брайан, Нейт, Нил, Раджу, Сара, Скотт
Результат по-прежнему хорош, но этот код не льстит. Спаси нас, Ява.
Нам больше не придется терпеть эту боль. Класс StringJoiner в Java 8 помогает нам решить эти проблемы. Мало того, класс String также добавляет метод соединения, чтобы мы могли заменить вышеуказанный материал одной строкой кода.
Скопируйте код кода следующим образом:
System.out.println(String.join(", ", друзья));
Приходите посмотреть, результаты так же удовлетворительны, как и код.
Скопируйте код кода следующим образом:
Брайан, Нейт, Нил, Раджу, Сара, Скотт
Результат по-прежнему хорош, но этот код не льстит. Спаси нас, Ява.
Нам больше не придется терпеть эту боль. Класс StringJoiner в Java 8 помогает нам решить эти проблемы. Мало того, класс String также добавляет метод соединения, чтобы мы могли заменить все вышеперечисленное одной строкой кода.
Скопируйте код кода следующим образом:
System.out.println(String.join(", ", друзья));
Приходите посмотреть, результаты так же удовлетворительны, как и код.
Скопируйте код кода следующим образом:
Брайан, Нейт, Нил, Раджу, Сара, Скотт
В базовой реализации метод String.join() вызывает класс StringJoiner для объединения значения, переданного в качестве второго параметра (который является параметром переменной длины), в длинную строку, используя первый параметр в качестве разделителя. Конечно, этот метод — нечто большее, чем просто соединение запятых. Например, мы можем передать несколько путей и легко указать путь к классам благодаря этим недавно добавленным методам и классам.
Мы уже знаем, как соединять элементы списка. Прежде чем соединять списки, мы также можем преобразовать их. Конечно, мы также знаем, как использовать метод карты для преобразования списков. Далее мы также можем использовать метод filter() для фильтрации нужных элементов. Последний шаг соединения элементов списка с помощью запятых или другого разделителя — это простая операция сокращения.
Мы можем использовать метод уменьшить() для объединения элементов в строку, но это требует некоторой работы с нашей стороны. В JDK есть очень удобный метод Collect(), который также является вариантом уменьшения(). Мы можем использовать его для объединения элементов в желаемое значение.
Метод Collect() выполняет операцию сокращения, но делегирует выполнение конкретной операции сборщику. Мы можем объединить преобразованные элементы в ArrayList. Продолжая предыдущий пример, мы можем объединить преобразованные элементы в строку, разделенную запятыми.
Скопируйте код кода следующим образом:
System.out.println(
друзья.поток()
.map(String::toUpperCase)
.collect(joining(", ")));
Мы вызвали метод Collect() для преобразованного списка, передав ему сборщик, возвращаемый методом joining(). Joining — это статический метод в классе инструментов Collectors. Сборщик похож на получателя. Он получает объекты, переданные сборщиком, и сохраняет их в нужном вам формате: ArrayList, String и т. д. Мы рассмотрим этот метод далее в методе Collect и классе Collectors на странице 52.
Это имя вывода, теперь оно написано в верхнем регистре и разделено запятыми.
Скопируйте код кода следующим образом:
БРАЙАН, НЕЙТ, НИЛ, РАДЖУ, САРА, СКОТТ
Подвести итог
Коллекции очень распространены в программировании. Благодаря лямбда-выражениям операции с коллекциями в Java стали проще и проще. Весь неуклюжий старый код операций сбора данных можно заменить этим элегантным и лаконичным новым подходом. Внутренний итератор делает обход и преобразование коллекции более удобным, избавляет от проблем с изменчивостью, а поиск элементов коллекции становится чрезвычайно простым. Используя эти новые методы, вы сможете писать гораздо меньше кода. Это делает код более простым в обслуживании, более ориентированным на бизнес-логику и меньшим количеством базовых операций в программировании.
В следующей главе мы увидим, как лямбда-выражения упрощают еще одну базовую операцию при разработке программ: манипулирование строками и сравнение объектов.