Получение списка из java.util.stream.Stream в Java 8


443

Я играл с лямбдами Java 8, чтобы легко фильтровать коллекции. Но я не нашел краткий способ получить результат в виде нового списка в том же утверждении. Вот мой самый лаконичный подход:

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
List<Long> targetLongList = new ArrayList<>();
sourceLongList.stream().filter(l -> l > 100).forEach(targetLongList::add);

Примеры в сети не ответили на мой вопрос, потому что они останавливаются без создания нового списка результатов. Должен быть более лаконичный способ. Я бы ожидал, что Streamкласс имеет методы , как toList(), toSet()...

Есть ли способ, которым переменные targetLongListмогут быть непосредственно назначены третьей строкой?


7
В случае, если вам не нужно sourceLongListпотом есть Collection.removeIf(…)для удобства.
Хольгер

2
Как насчет этого? List<Long> targetLongList = sourceLongList.stream().collect(Collectors.toList());
leeyuiwah

Ответы:


609

То, что вы делаете, может быть самым простым способом, при условии, что ваш поток остается последовательным - в противном случае вам придется предварительно вызвать вызов sequential () forEach.

[позднее редактирование: причина, по которой вызов sequential () необходим, заключается в том, что код в том виде, в котором он стоит ( forEach(targetLongList::add)), был бы нечетким, если бы поток был параллельным. Даже в этом случае не будет достигнут ожидаемый эффект, поскольку forEachон явно недетерминирован - даже в последовательном потоке порядок обработки элементов не гарантируется. Вы должны использовать forEachOrderedдля обеспечения правильного заказа. Целью разработчиков Stream API является то, что вы будете использовать коллектор в этой ситуации, как показано ниже.]

Альтернатива

targetLongList = sourceLongList.stream()
    .filter(l -> l > 100)
    .collect(Collectors.toList());

10
Дополнение: я думаю, что этот код становится немного короче, яснее и красивее, если вы используете статический импорт toList. Это делается путем размещения следующих среди импорта файла: static import java.util.stream.Collectors.toList;. Тогда вызов вызова читает просто .collect(toList()).
Лий

4
В Eclipse можно заставить IDE добавить статический импорт для методов. Это можно сделать, добавив Collectorsкласс в « Предпочтения» -> « Java» -> « Редактор» -> « Помощник по содержимому» -> « Избранное» . После этого вам нужно всего лишь toLiнажать Ctrl + Space, чтобы заполнить IDE toListи добавить статический импорт.
Лий

3
Нужно иметь в виду, что у IntStreamнекоторых и других почти, но не совсем Stream, нет collect(Collector)метода, и вам придется вызывать IntStream.boxed()их, чтобы Streamсначала преобразовать их в обычный . Опять же, может быть, вы просто хотите toArray().
Мутант Боб

почему мы должны использовать sequential()раньше forEachили использовать forEachOrdered
Амарнат Хариш

1
@amarnathharish Потому что forEach не гарантирует порядок выполнения операций для параллельного потока. JavaDoc говорит: «Поведение этой операции явно недетерминировано. Для параллельных потоковых конвейеров эта операция не гарантирует соблюдение порядка встречи потока, так как это принесет ущерб преимуществам параллелизма». (Первое предложение этой цитаты фактически означает, что порядок не гарантируется и для последовательных потоков, хотя на практике он сохраняется.)
Морис Нафталин

183

Обновлено:

Другой подход заключается в использовании Collectors.toList:

targetLongList = 
    sourceLongList.stream().
    filter(l -> l > 100).
    collect(Collectors.toList());

Предыдущее решение:

Другой подход заключается в использовании Collectors.toCollection:

targetLongList = 
    sourceLongList.stream().
    filter(l -> l > 100).
    collect(Collectors.toCollection(ArrayList::new));

60
Это, однако, полезно, если вы хотите конкретную реализацию List.
orbfish

1
Несмотря на то, что рекомендуется кодировать с помощью интерфейсов, существуют явные случаи (одним из них является GWT), когда вам приходится кодировать против конкретных реализаций (если только вы не хотите, чтобы все реализации List компилировались и доставлялись как javascript).
Эдуард Коренский

3
Другой профи этого метода, из Collectors::toListjavadoc: «Нет никаких гарантий относительно типа, изменчивости, сериализуемости или безопасности потока возвращаемого списка; если требуется больше контроля над возвращенным списком, используйте toCollection(Supplier)».
Чарльз Вуд

12

Мне нравится использовать метод util, который возвращает коллектор, ArrayListкогда это то, что я хочу.

Я думаю, что использование решения Collectors.toCollection(ArrayList::new)немного слишком шумно для такой обычной операции.

Пример:

ArrayList<Long> result = sourceLongList.stream()
    .filter(l -> l > 100)
    .collect(toArrayList());

public static <T> Collector<T, ?, ArrayList<T>> toArrayList() {
    return Collectors.toCollection(ArrayList::new);
}

Этим ответом я также хочу продемонстрировать, насколько просто создавать и использовать пользовательские коллекторы, что в целом очень полезно.


Если вы объявляете результат как List <Long>, вам не нужно использовать этот метод util. Collectors.toList сделает. Кроме того, использование определенных классов вместо интерфейсов является запахом кода.
Луис Мартинес

1
@LluisMartinez: «Collectors.toList сделает». Нет, не во многих ситуациях. Потому что это не очень хорошая идея, toListесли вы, например, хотите изменить список позже в программе. toListДокументация говорит следующее: «Там нет никаких гарантий по типу, изменчивости, сериализуемости или потокобезопасности Списка возвращаемых, если больший контроль над возвращаемым списком требуется, использование toCollection, Мой ответ демонстрирует способ сделать это более удобным в обычном случае.
Лии

Если вы хотите специально создать ArrayList, тогда все в порядке.
Луис Мартинес

5

Если у вас есть массив примитивов, вы можете использовать коллекции примитивов, доступные в Eclipse Collections .

LongList sourceLongList = LongLists.mutable.of(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
LongList targetLongList = sourceLongList.select(l -> l > 100);

Если вы не можете изменить sourceLongList с List:

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
List<Long> targetLongList = 
    ListAdapter.adapt(sourceLongList).select(l -> l > 100, new ArrayList<>());

Если вы хотите использовать LongStream:

long[] sourceLongs = new long[]{1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L};
LongList targetList = 
    LongStream.of(sourceLongs)
    .filter(l -> l > 100)
    .collect(LongArrayList::new, LongArrayList::add, LongArrayList::addAll);

Примечание: я участвую в коллекциях Eclipse.


4

Немного более эффективный способ (избегайте создания исходного списка и автоматической распаковки с помощью фильтра):

List<Long> targetLongList = LongStream.of(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L)
    .filter(l -> l > 100)
    .boxed()
    .collect(Collectors.toList());


1

Если вы не возражаете против использования сторонних библиотек, в библиотеке AOL cyclops-реагировать (раскрытие которой я участвую) есть расширения для всех типов JDK Collection , включая List . Интерфейс ListX расширяет java.util.List и добавляет большое количество полезных операторов, включая фильтр.

Вы можете просто написать

ListX<Long> sourceLongList = ListX.of(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
ListX<Long> targetLongList = sourceLongList.filter(l -> l > 100);

ListX также может быть создан из существующего списка (через ListX.fromIterable)


1

Существует еще один вариант метода сбора данных, предоставляемый классом LongStream, а также классами IntStream и DoubleStream.

<R> R collect(Supplier<R> supplier,
              ObjLongConsumer<R> accumulator,
              BiConsumer<R,R> combiner)

Выполняет изменяемую операцию сокращения над элементами этого потока. Изменяемое сокращение - это такое, в котором уменьшенное значение представляет собой изменяемый контейнер результатов, такой как ArrayList, и элементы включаются путем обновления состояния результата, а не путем замены результата. Это дает результат, эквивалентный:

R result = supplier.get();
  for (long element : this stream)
       accumulator.accept(result, element);
  return result;

Как и Reduce (long, LongBinaryOperator), операции сбора могут быть распараллелены без дополнительной синхронизации. Это терминальная операция.

И ответ на ваш вопрос с этим методом сбора, как показано ниже:

    LongStream.of(1L, 2L, 3L, 3L).filter(i -> i > 2)
    .collect(ArrayList::new, (list, value) -> list.add(value)
    , (list1, list2) -> list1.addAll(list2));

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

     LongStream.of(1L, 2L, 3L, 3L).filter(i -> i > 2)
    .collect(ArrayList::new, List::add , List::addAll);

Ниже будет вариант HashSet:

     LongStream.of(1L, 2L, 3L, 3).filter(i -> i > 2)
     .collect(HashSet::new, HashSet::add, HashSet::addAll);

Аналогично вариант LinkedList выглядит так:

     LongStream.of(1L, 2L, 3L, 3L)
     .filter(i -> i > 2)
     .collect(LinkedList::new, LinkedList::add, LinkedList::addAll);

0

Если кто-то (например, я) ищет способы взаимодействия с объектами вместо примитивных типов, тогда используйте mapToObj()

String ss = "An alternative way is to insert the following VM option before "
        + "the -vmargs option in the Eclipse shortcut properties(edit the "
        + "field Target inside the Shortcut tab):";

List<Character> ll = ss
                        .chars()
                        .mapToObj(c -> new Character((char) c))
                        .collect(Collectors.toList());

System.out.println("List type: " + ll.getClass());
System.out.println("Elem type: " + ll.get(0).getClass());
ll.stream().limit(50).forEach(System.out::print);

печатает:

List type: class java.util.ArrayList
Elem type: class java.lang.Character
An alternative way is to insert the following VM o

0
String joined = 
                Stream.of(isRead?"read":"", isFlagged?"flagged":"", isActionRequired?"action":"", isHide?"hide":"")
                      .filter(s -> s != null && !s.isEmpty())
                      .collect(Collectors.joining(","));

0

Вот код от AbacusUtil

LongStream.of(1, 10, 50, 80, 100, 120, 133, 333).filter(e -> e > 100).toList();

Раскрытие информации: я разработчик AbacusUtil.


Я не нахожу никакого метода toList в классе LongStream. Не могли бы вы запустить этот код?
Vaneet Kataria

@VaneetKataria попробуй com.landawn.abacus.util.stream.LongStreamили LongStreamExв AbacusUtil
user_3380739

0

Вы можете переписать код, как показано ниже:

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
List<Long> targetLongList = sourceLongList.stream().filter(l -> l > 100).collect(Collectors.toList());

Спасибо за ваш вклад. Однако, пожалуйста, объясните, что вы изменили и насколько это относится к вопросу.
B -

Сначала я конвертировал свой ArrayList, чтобы обработать их с помощью фильтра и отфильтровал необходимые данные. Наконец, я использовал метод сбора потока Java 8 для сбора данных в новый список, называемый targetLongList.
Пратик Павар

0

Чтобы собрать в изменяемый список:

targetList = sourceList.stream()
                       .filter(i -> i > 100) //apply filter
                       .collect(Collectors.toList());

Собрать в неизменном списке:

targetList = sourceList.stream()
                       .filter(i -> i > 100) //apply filter
                       .collect(Collectors.toUnmodifiableList());

Объяснение collectиз JavaDoc :

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

Это терминальная операция.

При параллельном выполнении несколько промежуточных результатов могут быть созданы, заполнены и объединены для обеспечения изоляции изменяемых структур данных. Следовательно, даже при параллельном выполнении с не поточно-ориентированными структурами данных (такими как ArrayList) дополнительная синхронизация не требуется.


-3

Если вы не используете parallel()это будет работать

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);

List<Long> targetLongList =  new ArrayList<Long>();

sourceLongList.stream().peek(i->targetLongList.add(i)).collect(Collectors.toList());

Мне не нравится, что collect () используется только для управления потоком, так что крюк peek () вызывается для каждого элемента. Результат работы терминала отбрасывается.
Даниэль К.

Это очень странно вызывать, collectа затем не сохранять возвращаемое значение. В этом случае вы можете использовать forEachвместо этого. Но это все еще плохое решение.
Лий

1
Использование peek () таким способом является антипаттерном.
Гжегож Пивоварек

В соответствии с потоком Java метод просмотра документов должен использоваться только для целей отладки. Он не должен использоваться ни для какой обработки, кроме отладки.
Vaneet Kataria
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.