У меня есть список, в myListToParse
котором я хочу отфильтровать элементы и применить метод для каждого элемента, и добавить результат в другой список myFinalList
.
С Java 8 я заметил, что могу сделать это двумя разными способами. Я хотел бы знать более эффективный путь между ними и понять, почему один путь лучше, чем другой.
Я открыт для любого предложения о третьем пути.
Способ 1:
myFinalList = new ArrayList<>();
myListToParse.stream()
.filter(elt -> elt != null)
.forEach(elt -> myFinalList.add(doSomething(elt)));
Способ 2:
myFinalList = myListToParse.stream()
.filter(elt -> elt != null)
.map(elt -> doSomething(elt))
.collect(Collectors.toList());
java
java-8
java-stream
Эмильен Бандит
источник
источник
elt -> elt != null
его можно заменить наObjects::nonNull
Optional<T>
вместо этого в сочетании сflatMap
..map(this::doSomething)
предполагая, чтоdoSomething
это нестатический метод. Если он статический, вы можете заменитьthis
его именем класса.Ответы:
Не беспокойтесь о различиях в производительности, в этом случае они будут минимальными.
Способ 2 предпочтительнее, потому что
это не требует мутирования коллекции, которая существует вне лямбда-выражения,
это более читабельно, потому что различные шаги, выполняемые в конвейере сбора, записываются последовательно: сначала операция фильтрации, затем операция отображения, а затем сбор результата (дополнительную информацию о преимуществах конвейеров сбора см. в замечательной статье Мартина Фаулера ),
Вы можете легко изменить способ сбора значений, заменив используемый метод
Collector
. В некоторых случаях вам может потребоваться написать свое собственноеCollector
, но выгода в том, что вы можете легко использовать это повторно.источник
Я согласен с существующими ответами, что вторая форма лучше, потому что она не имеет никаких побочных эффектов и ее легче распараллелить (просто используйте параллельный поток).
По производительности, кажется, они эквивалентны, пока вы не начнете использовать параллельные потоки. В этом случае карта будет работать намного лучше. Смотрите ниже результаты микро-теста :
Вы не можете повысить первый пример таким же образом, потому что forEach - это терминальный метод - он возвращает void - поэтому вы вынуждены использовать лямбду с сохранением состояния. Но это действительно плохая идея, если вы используете параллельные потоки .
И наконец, обратите внимание, что ваш второй фрагмент может быть написан чуть более кратким способом со ссылками на методы и статическим импортом:
источник
Одним из основных преимуществ использования потоков является то, что он дает возможность обрабатывать данные декларативным способом, то есть с использованием функционального стиля программирования. Это также дает возможность многопоточности бесплатно, что означает, что нет необходимости писать дополнительный многопоточный код, чтобы сделать ваш поток параллельным.
Предполагая, что причиной изучения этого стиля программирования является то, что вы хотите использовать эти преимущества, тогда ваш первый пример кода потенциально не функционален, поскольку
foreach
метод классифицируется как терминальный (то есть он может вызывать побочные эффекты).Второй способ предпочтителен с точки зрения функционального программирования, поскольку функция map может принимать лямбда-функции без сохранения состояния. Более конкретно, лямбда, переданная в функцию карты, должна быть
ArrayList
).Другое преимущество второго подхода состоит в том, что если поток параллелен, а коллектор является параллельным и неупорядоченным, то эти характеристики могут предоставить полезные советы для операции сокращения для одновременного выполнения сбора.
источник
Если вы используете Eclipse Collections, вы можете использовать
collectIf()
метод.Он оценивает с нетерпением и должен быть немного быстрее, чем при использовании потока.
Примечание: я являюсь коммиттером для Eclipse Collections.
источник
Я предпочитаю второй способ.
Когда вы используете первый способ, если вы решите использовать параллельный поток для повышения производительности, вы не будете иметь никакого контроля над порядком добавления элементов в список вывода
forEach
.Когда вы используете
toList
, Streams API сохранит порядок, даже если вы используете параллельный поток.источник
forEachOrdered
вместо того, чтобы,forEach
если он хотел использовать параллельный поток, но все же сохранить порядок. Но как документация дляforEach
государств, сохранение порядка встреч жертвует преимуществом параллелизма. Я подозреваю, что это также имеет место сtoList
тогда.Существует третий вариант - использование
stream().toArray()
- см. Комментарии, почему в stream не было метода toList . Оказывается, он медленнее, чем forEach () или collect (), и менее выразителен. Он может быть оптимизирован в последующих сборках JDK, поэтому добавьте его на всякий случай.при условии,
List<String>
с микро-микро тестом, 1М записей, 20% нулей и простым преобразованием в doSomething ()
результаты
параллельно:
последовательный:
параллельный без нулей и фильтра (так что поток
SIZED
): toArrays имеет лучшую производительность в таком случае, и.forEach()
завершается неудачно с "indexOutOfBounds" на получателе ArrayList, пришлось заменить на.forEachOrdered()
источник
Может быть Метод 3.
Я всегда предпочитаю держать логику отдельно.
источник
Если с помощью 3rd Pary Libaries все в порядке, циклоп-реакция определяет расширенные коллекции Lazy с этой встроенной функциональностью. Например, мы могли бы просто написать
ListX myListToParse;
ListX myFinalList = myListToParse.filter (elt -> elt! = Null) .map (elt -> doSomething (elt));
myFinalList не оценивается до первого доступа (и там после того, как материализованный список кэшируется и используется повторно).
[Раскрытие Я ведущий разработчик циклоп-реакции]
источник