Когда стримы использовать не стоит java
Перейти к содержимому

Когда стримы использовать не стоит java

  • автор:

Как эффективно работать со стримами — Java: Стримы

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

Приоритет читаемости и ясности

Сложные потоки разбивайте на более мелкие и понятные шаги. Пример: Если у вас есть список строк, и вы хотите отфильтровать те, которые начинаются на «A», а затем отсортировать их, лучше разделить эти шаги:

var strings = List.of("Apple", "Banana", "Avocado"); var filteredSorted = strings.stream() .filter(s -> s.startsWith("A")) .sorted() .toList(); 

Избегайте побочных эффектов

Операции с потоками должны избегать изменения исходных данных. Пример: Вместо изменения списка в forEach() , используйте map для создания нового списка:

var numbers = List.of(1, 2, 3); var doubled = numbers.stream() .map(n -> n * 2) .toList(); 

Читаемость важнее производительности

Используйте потоки для улучшения читаемости кода. Пример: Использование потоков для фильтрации и преобразования коллекций:

var names = List.of("John", "Jane", "Adam", "Alice"); var shortNames = names.stream() .filter(name -> name.length()  4) .toList(); 

Используйте ссылки на методы

Для упрощения кода используйте ссылки на методы. Пример: Преобразование строк в верхний регистр:

var names = List.of("John", "Jane"); var upperCaseNames = names.stream() .map(String::toUpperCase) .toList(); 

Ограничьте область применения потоков

Используйте потоки для сложных операций с коллекциями. Пример: Неправильно использовать потоки для простой итерации:

// Неэффективное использование потоков var numbers = List.of(1, 2, 3); numbers.stream().forEach(System.out::println); // Достаточно forEach() у списка numbers.forEach(System.out::println); 

Будьте осторожны с терминальными операциями

После терминальной операции поток нельзя использовать повторно. Пример: После использования collect, исходный поток больше не доступен:

var stream = Stream.of("a", "b", "c"); var list = stream.toList(); // stream уже не может быть использован здесь для других операций 

Используйте невмешательные операции

Не изменяйте источник потока во время его обработки. Пример: Изменение списка во время обработки потока может привести к непредсказуемым результатам

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов

Наши выпускники работают в компаниях:

Используйте Stream API проще (или не используйте вообще)

С появлением Java 8 Stream API позволило программистам писать существенно короче то, что раньше занимало много строк кода. Однако оказалось, что многие даже с использованием Stream API пишут длиннее, чем надо. Причём это не только делает код длиннее и усложняет его понимание, но иногда приводит к существенному провалу производительности. Не всегда понятно, почему люди так пишут. Возможно, они прочитали только небольшой кусок документации, а про другие возможности не слышали. Или вообще документацию не читали, просто видели где-то пример и решили сделать похоже. Иногда это напоминает анекдот про «задача сведена к предыдущей».

В этой статье я собрал те примеры, с которыми столкнулся в практике. Надеюсь, после такого ликбеза код программистов станет чуточку красивее и быстрее. Большинство этих штук хорошая IDE поможет вам исправить, но IDE всё-таки не всесильна и голову не заменяет.

1. Стрим из коллекции без промежуточных операций обычно не нужен

Если у вас промежуточных операций нет, часто можно и нужно обойтись без стрима.

1.1. collection.stream().forEach()

Хотите что-то сделать для всех элементов коллекции? Замечательно. Но зачем вам стрим? Напишите просто collection.forEach() . В большинстве случаев это одно и то же, но короче и производит меньше мусора. Некоторые боятся, что различие в функциональности есть, но не могут толком объяснить, какое оно. Говорят, мол, forEach не гарантирует порядок. Как раз в стриме по спецификации не гарантирует (по факту он есть), а без стрима для упорядоченных коллекций гарантирует. Если порядок вам не нужен, вам не станет хуже, если он появится. Единственное отличие из стандартной библиотеки, которое мне известно — это синхронизированные коллекции, созданные через Collections.synchronizedXyz() . В этом случае collection.forEach() синхронизирует всю операцию, тогда как collection.stream().forEach() не синхронизирует ничего. Скорее всего, если вы уж используете синхронизированные коллекции, вам всё-таки синхронизация нужна, поэтому станет только лучше.

1.2. collection.stream().collect(Collectors.toList())

Собираетесь преобразовать произвольную коллекцию в список? Замечательно. Начиная с Java 1.2 у вас есть отличная возможность для этого: new ArrayList<>(collection) (ну хорошо, до Java 5 дженериков не было). Это не только короче, но и быстрее и опять же создаст меньше мусора в куче. Может быть значительно меньше, так как в большинстве случаев у вас выделится один массив нужного размера, тогда как стрим будет добавлять элементы по одному, растягивая по мере необходимости. Аналогично вместо stream().collect(toSet()) создаём new HashSet<>() , а вместо stream().collect(toCollection(TreeSet::new)) — new TreeSet<>() .

1.3. collection.stream().toArray(String[]::new)

Новый способ преобразования в массив ничем не лучше старого доброго collection.toArray(new String[0]) . Опять же: так как на пути меньше абстракций, преобразование может оказаться более эффективно. Во всяком случае объект стрима вам не нужен.

1.4. collection.stream().max(Comparator.naturalOrder()).get()

Есть замечательный метод Collections.max , который почему-то незаслуженно многими забыт. Вызов Collections.max(collection) сделает то же самое и опять же с меньшим количеством мусора. Если у вас свой компаратор, используйте Collections.max(collection, comparator) . Метод Collections.max() подойдёт хуже, если вы хотите специально обработать пустую коллекцию, тогда стрим более оправдан. Цепочка collection.stream().max(comparator).orElse(null) смотрится лучше, чем collection.isEmpty() ? null : Collections.max(collection, comparator) .

1.5. collection.stream().count()

Это совсем ни в какие ворота не лезет: есть ведь collection.size() ! Если в Java 9 count() отработает быстро, то в Java 8 этот вызов всегда пересчитывает все элементы, даже если размер очевиден. Не делайте так.

2. Поиск элемента

2.1. stream.filter(condition).findFirst().isPresent()

Такой код вижу на удивление часто. Суть его: проверить, выполняется ли условие для какого-то элемента стрима. Именно для этого есть специальный метод: stream.anyMatch(condition) . Зачем вам Optional ?

2.2. !stream.anyMatch(condition)

Тут некоторые поспорят, но я считаю, что использовать специальный метод stream.noneMatch(condition) более выразительно. А вот если и в условии отрицание: !stream.anyMatch(x -> !condition(x)) , то тут однозначно лучше написать stream.allMatch(x -> condition(x)) . Тот, кто будет читать код, скажет вам спасибо.

2.3. stream.map(condition).anyMatch(b -> b)

И такой странный код иногда пишут, чтобы запутать коллег. Если увидите такое, знайте, что это просто stream.anyMatch(condition) . Здесь же вариации на тему вроде stream.map(condition).noneMatch(Boolean::booleanValue) или stream.map(condition).allMatch(Boolean.TRUE::equals) .

3. Создание стрима

3.1. Collections.emptyList().stream()

Нужен пустой стрим? Бывает, ничего страшного. И для этого есть специальный метод Stream.empty() . Производительность одинаковая, но короче и понятнее. Метод emptySet здесь не отличается от emptyList .

3.2. Collections.singleton(x).stream()

И тут можно упростить жизнь: если вам потребовался стрим из одного элемента, пишите просто Stream.of(x) . Опять же без разницы singleton или singletonList : когда в стриме один элемент, никого не волнует, упорядочен стрим или нет.

3.3. Arrays.asList(array).stream()

Развитие этой же темы. Люди почему-то так делают, хотя Arrays.stream(array) или Stream.of(array) отработает не хуже. Если вы указываете элементы явно ( Arrays.asList(x, y, z).stream() ), то Stream.of(x, y, z) тоже сработает. Аналогично с EnumSet.of(x, y, z).stream() . Вам же стрим нужен, а не коллекция, так и создавайте сразу стрим.

3.4. Collections.nCopies(N, «ignored»).stream().map(ignored -> new MyObject())

Нужен стрим из N одинаковых объектов? Тогда nCopies() — ваш выбор. А вот если нужно сгенерировать стрим из N объектов, созданных одним и тем же способом, то тут красивее и оптимальнее воспользоваться Stream.generate(() -> new MyObject()).limit(N) .

3.5. IntStream.range(from, to).mapToObj(idx -> array[idx])

Нужен стрим из куска массива? Есть специальный метод Arrays.stream(array, from, to) . Опять же короче и меньше мусора, плюс так как массив больше не захвачен лямбдой, он не обязан быть effectively-final. Понятно, если from — это 0, а to — это array.length , тогда вам просто нужен Arrays.stream(array) , причём тут код станет приятнее, даже если в mapToObj что-то более сложное. Например, IntStream.range(0, strings.length).mapToObj(idx -> strings[idx].trim()) легко превращается в Arrays.stream(strings).map(String::trim) .

Более хитрая вариация на тему — IntStream.range(0, Math.min(array.length, max)).mapToObj(idx -> array[idx]) . Немножко подумав, понимаешь, что это Arrays.stream(array).limit(max) .

4. Ненужные и сложные коллекторы

Иногда люди изучают коллекторы и всё пытаются делать через них. Однако не всегда они нужны.

4.1. stream.collect(Collectors.counting())

Многие коллекторы нужны только как вторичные в сложных каскадных операциях вроде groupingBy . Коллектор counting() как раз из них. Пишите stream.count() и не мучайтесь. Опять же если в Java 9 count() может иногда выполниться за константное время, то коллектор всегда будет пересчитывать элементы. А в Java 8 коллектор counting() ещё и боксит зазря (я это исправил в Java 9). Из этой же оперы коллекторы maxBy() , minBy() (есть методы max() и min() ), reducing() (используйте reduce() ), mapping() (просто добавьте шаг map() , а затем воспользуйтесь вторичным коллектором напрямую). В Java 9 добавились filtering() и flatMapping() , которые также дублируют соответствующие промежуточные операции.

4.2. groupingBy(classifier, collectingAndThen(maxBy(comparator), Optional::get))

Частая задача: хочется сгруппировать элементы по классификатору, выбрав в каждой группе максимум. В SQL это выглядит просто SELECT classifier, MAX(. ) FROM . GROUP BY classifier . Видимо, пытаясь перенести опыт SQL, люди пытаются использовать тот же самый groupingBy и в Stream API. Казалось бы должно сработать groupingBy(classifier, maxBy(comparator)) , но нет. Коллектор maxBy возвращает Optional . Но мы-то знаем, что вложенный Optional всегда не пуст, так как в каждой группе по крайней мере один элемент есть. Поэтому приходится добавлять некрасивые шаги вроде collectingAndThen , и всё начинает выглядеть совсем чудовищно.

Однако отступив на шаг назад, можно понять, что groupingBy тут не нужен. Есть другой замечательный коллектор — toMap , и это как раз то что надо. Мы просто хотим собрать элементы в Map , где ключом будет классификатор, а значением сам элемент. В случае же дубликата выберем больший из них. Для этого, кстати, есть BinaryOperator.maxBy(comparator) , который можно статически импортировать вместо одноимённого коллектора. В результате имеем: toMap(classifier, identity(), maxBy(comparator)) .

Если вы порываетесь использовать groupingBy , а вторичным коллектором у вас maxBy , minBy или reducing (возможно, с промежуточным mapping ), посмотрите в сторону коллектора toMap — может полегчать.

5. Не считайте то, что не нужно считать

5.1. listOfLists.stream().flatMap(List::stream).count()

Это перекликается с пунктом 1.5. Мы хотим посчитать суммарное число элементов во вложенных коллекциях. Казалось бы всё логично: растянем эти коллекции в один стрим с помощью flatMap и пересчитаем. Однако в большинстве случаев размеры вложенных списков уже посчитаны, хранятся у них в поле и легко доступны с помощью метода size() . Небольшая модификация существенно увеличит скорость операции: listOfLists.stream().mapToInt(List::size).sum() . Если боитесь, что int переполнится, mapToLong тоже сработает.

5.2. if(stream.filter(condition).count() > 0)

Опять же забавный способ записать stream.anyMatch(condition) . Но в отличие от довольно безобидного 2.1 вы тут теряете короткое замыкание: будут перебраны все элементы, даже если условие сработало на самом первом. Аналогично если вы проверяете filter(condition).count() == 0 , лучше воспользоваться noneMatch(condition) .

5.3. if(stream.count() > 2)

Этот случай более хитрый. Вам теперь важно знать, больше двух элементов в стриме или нет. Если вас волнует производительность, возможно, стоит вставить stream.limit(3).count() . Вам ведь не важно, сколько их, если их больше двух.

6. Разное

6.1. stream.sorted(comparator).findFirst()

Что хотел сказать автор? Отсортируй стрим и возьми первый элемент. Это же всё равно что взять минимальный элемент: stream.min(comparator) . Иногда видишь даже stream.sorted(comparator.reversed()).findFirst() , что аналогично stream.max(comparator) . Реализация Stream API не соптимизирует тут (хотя могла бы), а сделает всё как вы сказали: соберёт стрим в промежуточный массив, отсортирует его весь и выдаст вам первый элемент. Вы существенно потеряете в памяти и скорости на такой операции. Ну и, конечно, замена существенно понятнее.

Некоторые люди пытаются выполнить какой-нибудь побочный эффект в стриме. Вообще это в принципе уже звоночек, что может стрим вам вовсе и не нужен. Но так или иначе для этих целей есть специальный метод peek . Пишем stream.peek(counter::addAndGet) .

На этом у меня всё. Если вы сталкивались со странными и неэффективными способами использования Stream API, напишите о них в комментариях.

Кофе-брейк #108. 12 распространенных способов использования Java Streams, как оценить выделение памяти объекта в Java

Java-университет

12 распространенных способов использования Java Streams

Кофе-брейк #108. 12 распространенных способов использования Java Streams, как оценить выделение памяти объекта в Java - 1

Источник: Dev.to Java Streams API впервые появился в Java 8. Его цель — предоставить более компактный способ выполнения общих операций с коллекциями объектов. Также Java Streams API может использоваться для реализации сложных алгоритмов. В этой статье мы поговорим о распространенных случаях использования Java Streams. Для начала проясним некоторые основы:

  • stream() — создает поток (Stream) из коллекции.
  • collect() — собирает поток в объект. Объект может быть коллекцией, примитивом или пользовательским классом.
  • Collectors — класс, который предоставляет (множество) статических методов для сбора потоков.

А теперь давайте рассмотрим некоторые варианты использования Streams:

1. Фильтрация (Filtering)

  • Используется для удаления значений из коллекции (Collection) на основе условия.
  • Для фильтрации элементов коллекции на основе условия используется метод filter() . Сохраняются только совпадающие элементы.
 List evenNumbers = originalList.stream() .filter(n -> n % 2 == 0) .collect(Collectors.toList()); 

2. Предварительная обработка (Preprocessing)

  • Полезна, когда каждое значение в коллекции необходимо изменить на месте.
  • Метод map() используется для применения функции к каждому элементу коллекции и возврата новой коллекции вычисленных значений.
 List squares = originalList.stream() .map(n -> n * n) .collect(Collectors.toList()); 

3. Преобразование (Conversion)

  • Полезно, когда мы хотим преобразовать коллекцию в другую коллекцию.
  • Есть несколько способов добиться этого.
Пример 1. Создать Map из Lists.

Преобразование списка строк в карту строк и длины.

 Map wordLengths = words.stream() .collect(Collectors.toMap( word -> word, word -> word.length())); 
Пример 2. Преобразование списка (list) в наборы (sets).

Это распространенный вариант использования для удаления дубликатов. Кроме того, если мы хотим поместить элементы обратно в список, мы можем дважды использовать методы stream() и collect() . К примеру, давайте преобразуем список строк в список уникальных строк:

 // if we want to collect to a set Set uniqueWords = words.stream() .collect(Collectors.toSet()); // OR // if we want to start and end as a list List uniqueWords = words.stream() .collect(Collectors.toSet()).stream().collect(Collectors.toList()); 
Пример 3. Преобразование списка товаров в список их названий. (Flattening — Выравнивание)
 List productNames = products.stream() .map(product -> product.getName()) .collect(Collectors.toList()); 
4. Сокращение (Reduction)
  • Сокращает Collection до одного значения.
  • Метод reduce() используется для применения функции к каждому элементу коллекции и возврата одного значения.
 int sum = numbers.stream() .reduce(0, (a, b) -> a + b); 
5. Группировка (Grouping)
  • Группирует элементы Collection по заданному условию.
  • Для группировки элементов Collection по условию используется метод Collectors.groupingBy() .
 Map> productsByCategory = products.stream() .collect(Collectors.groupingBy(product -> product.getCategory())); 

6. Поиск (Finding)

  • Ищет первый или любой элемент Collection, соответствующий условию.
  • Для поиска используются методы findFirst() и findAny() .
 Optional firstLongWord = words.stream() .filter(word -> word.length() > 5) .findFirst(); // Note that findFirst() and findAny() methods return Optional objects. 

7. Сортировка (Sorting)

  • Сортирует элементы Collections.
  • Для сортировки используется метод sorted() .
 List topK = numbers.stream() .sorted() .limit(k) .collect(Collectors.toList()); 

8. Разделение (Partitioning)

  • Разделяет элементы Collection по заданному условию.
  • Для разделения элементов используется метод Collectors.partitioningBy() .
 Map> passingFailing = students .stream() .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD)); 

9. Подсчет (Counting)

  • Подсчитывает количество элементов, соответствующих условию.
  • Для подсчета количества элементов, соответствующих условию, используется метод count() .
 long count = words.stream() .filter(word -> word.length() > 5) .count(); 

10. Диапазон (Range)

  • Создает диапазон значений.
  • Для создания диапазона значений используется метод range() .
 int[] numbers = IntStream.range(0, 10).toArray(); 

11. Соответствие (Matching)

  • Сопоставляет элементы коллекции с предикатом (условием).
  • Для сопоставления элементов коллекции с предикатом и возврата логического значения используются такие методы, как anyMatch() , allMatch() , и noneMatch() .

К примеру, проверим товары с ценой выше 10.

 // true when all elements match the predicate boolean allMatch = products.stream() .allMatch(product -> product.getPrice() > 10); // true when any element matches the predicate boolean anyMatch = products.stream() .anyMatch(product -> product.getPrice() > 10); // true when no elements match the predicate boolean noneMatch = products.stream() .noneMatch(product -> product.getPrice() > 10); 

12. Присоединение (Joining)

  • Объединяет элементы коллекции в строку.
  • Для объединения элементов коллекции в строку используется метод Collectors.joining() .
 String joinedWords = words.stream() .collect(Collectors.joining(" ")); 
  • Параллельные потоки (Parallel Streams);
  • Статистика;
  • Пользовательские коллекторы (Custom Collectors).

Преимущества потоков

  • Более компактный код — уменьшает объем кода, необходимого для обработки коллекции.
  • Меньше промежуточных переменных. Промежуточные переменные могут быть причиной совершения ошибок. Чем их меньше — тем проще избежать неожиданных ошибок.
  • Интуитивно понятный код. Некоторые разработчики не согласятся с тем, что потоки более интуитивно понятны, чем другие методы. Однако, как только мы привыкнем к ним, они станут гораздо более интуитивными, чем другие методы.

Как оценить выделение памяти объекта в Java

Источник: DZone В этой статье показаны три наиболее известных способа оценки выделения памяти объекта в Java.

Оценка памяти с помощью Profiler

Кофе-брейк #108. 12 распространенных способов использования Java Streams, как оценить выделение памяти объекта в Java - 2

Самый простой способ оценить память некоторых объектов — заглянуть прямо в память JVM с помощью профилировщика, такого как Visual VM. Проблема с этим подходом заключается в том, что вам нужно подключиться к работающей JVM, что может быть невозможно для производственных сред из соображений безопасности.

Оценка памяти с помощью Instruments

Другой способ оценить выделенную память для данного объекта — использовать Instruments. Проще говоря, нам нужно создать класс и скомпилировать его в JAR. После создания JAR мы должны выполнить нашу JVM вместе с этим JAR. Подробно об этом способе можно узнать здесь. Недостатком тут является необходимость добавления определенного jar-файла в JVM, что может быть неприемлемо для производства из-за проблем с безопасностью или связанных с этим проблем.

Оценка памяти с использованием JOL Library

Как еще один вариант, мы можем использовать JOL Library. Это очень мощная библиотека, которая может предоставить подробную оценку веса объекта и памяти, выделенной экземпляром объекта. Чтобы использовать библиотеку, нам нужно добавить зависимость:

  org.openjdk.jol jol-core 0.16  

После этого мы можем использовать ее следующим образом:

 out.println(GraphLayout.parseInstance(myObject).totalSize() / 1024000d + " MB") 

ObjectSizeCalculator из архива Twitter

В открытом репозитории Twitter GitHub есть класс инструмента ObjectSizeCalculator, который может оценить выделенную память для данного экземпляра объекта. Его использование не занимает много памяти или времени. Процесс оценки занимает секунды, даже для больших объектов. Использование этого класса довольно просто:

 ObjectSizeCalculator.getObjectSize(address) 

Я рекомендую этот способ, но имейте в виду, что он поддерживается только Java Hotspot, OpenJDK и TwitterJDK.

Свертка (reduce) — Java: Стримы

Операция reduce() (свертка) — это терминальная операция, которая обрабатывает элементы стрима и возвращает одно значение. Это может быть сумма всех чисел, объединение строк или любая другая операция, сводящая все элементы к одному результату. Фактически свертка используется во всех операциях агрегации данных. Рассмотрим пример нахождения суммы всех чисел без использования стримов:

var numbers = List.of(1, 2, 3, 4, 5); // Переменная для хранения суммы var sum = 0; // Цикл для суммирования чисел for (var number : numbers)  sum += number; > // Вывод суммы чисел System.out.println("Сумма чисел: " + sum); // => Сумма чисел: 15 

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

    У свертки есть начальное значение. В случае суммы это ноль, в случае умножения — единица и так далее.

var sum = 0; 
sum += number; 

Все это нужно для реализации reduce() , поэтому его использование отличается, например, от фильтрации.

var numbers = List.of(1, 2, 3, 4, 5); var sum = numbers.stream() .reduce(0, (subtotal, element) -> subtotal + element); // или используя Integer.sum(subtotal, element) // .reduce(0, Integer::sum); System.out.println(sum); // => Вывод чисел: 15 

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

var numbers = List.of(1, 2, 3, 4, 5); // Начальное значение - первый элемент списка var maxNumber = numbers.stream() .reduce(numbers.get(0), (a, b) -> a > b ? a : b); 

Здесь аккумулятор используется только для сравнения, если он меньше, то возвращается текущее значение, которое само становится аккумулятором.

Посмотрим на еще один пример для закрепления. В этом примере на вход подается список букв, которые собираются в строку через конкатенацию.

var letters = List.of("h", "e", "x", "l", "e", "t"); var result = letters.stream() // hexlet .reduce("", (result, element) -> result + element); // .reduce("", String::concat); 

В этом случае начальным значением будет пустая строка. Если мы захотим соединить слова в обратном порядке, то единственное, что мы поменяем это порядок конкатенации. Вместо result + element будет element + result .

Отличающиеся типы

В примерах выше, тип данных в коллекции и тип аккумулятора совпадали. Так бывает не всегда, в любой нетривиальной ситуации типы скорее всего будут отличаться. Возьмем для примера ситуацию, в которой у нас есть список товаров с указанием цены представленные строками. Нашей задачей будет извлечь из цены стоимость товара и найти общую сумму товаров в этом списке. Решим нашу задачу сначала без использования reduce .

var products = List.of("Laptop: 800", "Headphones: 50", "Smartphone: 500", "Mouse: 20"); var totalCost = 0; for (var product : products)  // Отделяем название от цены var parts = product.split(": "); // Splitting the string into product name and price // Преобразуем цену в Integer var price = Integer.parseInt(parts[1].trim()); // Extracting the price part and converting // Находим общую сумму totalCost += price; > System.out.println(totalCost); // => 1370 

Этот же код с помощью reduce() выглядит так:

var products = List.of("Laptop: 800", "Headphones: 50", "Smartphone: 500", "Mouse: 20"); var totalPrice = products.stream() .reduce(0, (sum, product) ->  var parts = product.split(": "); var price = Integer.parseInt(parts[1].trim()); return sum + price; >); 

Несмотря на то, что код выглядит правильно, он не компилируется с такой ошибкой:

for reduce(int,(sum,produ[. ]ce; >) .reduce(0, ^ method Stream.reduce(String,BinaryOperator) is not applicable (argument mismatch; int cannot be converted to String) 

Если не вдаваться в подробности, то связано это с тем, что типы выводятся таким образом:

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

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

Комбайнер помогает компилятору увидеть тип аккумулятора, поэтому код с комбайнером компилируется.

var products = List.of("Laptop: 800", "Headphones: 50", "Smartphone: 500", "Mouse: 20"); var totalPrice = products.stream() .reduce(0, (sum, product) ->  var parts = product.split(": "); var price = Integer.parseInt(parts[1].trim()); return sum + price; >, Integer::sum); 

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов

Наши выпускники работают в компаниях:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *