Отримання списку з 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

Те, що ви робите, може бути найпростішим способом, за умови, що ваш потік залишатиметься послідовним, інакше вам доведеться ставити виклик на секвенціональний () раніше forEach.

[пізніше редагувати: причина, необхідна для виклику послідовного (), полягає в тому, що код у такому стані ( forEach(targetLongList::add)) був би раціональним, якби потік був паралельним. Навіть тоді він не досягне наміченого ефекту, як forEachявно недетермінований - навіть у послідовному потоці порядок обробки елементів не гарантується. Вам потрібно буде використовувати forEachOrderedдля забезпечення правильного замовлення. Метою дизайнерів API Stream є те, що ви будете використовувати колектор у цій ситуації, як показано нижче.]

Альтернативою є

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при натисканні клавіші Ctr + Space, щоб IDE заповнив toListі додав статичний імпорт.
Лій

3
Одне, що слід пам’ятати, - це те, що IntStreamі деякі інші, але майже Streamне зовсім такі collect(Collector)методи не мають, і вам доведеться спочатку закликати IntStream.boxed()перетворити їх у звичайні Stream. Потім знову, можливо, ви просто хочете toArray().
Мутант Боб

чому ми маємо використовувати sequential()раніше forEachабо використовувати "forEachOrряд"
amarnath Harish

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
Однак це корисно, якщо ви хочете певної реалізації списку.
orbfish

1
Незважаючи на те, що рекомендується кодувати інтерфейси, є чіткі випадки (одним з них є GWT), коли вам доведеться кодувати проти конкретних реалізацій (якщо ви не хочете, щоб усі реалізації списку були зібрані та доставлені як 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);
}

За допомогою цієї відповіді я також хочу продемонструвати, наскільки просто створювати та використовувати спеціальні колекціонери, що в цілому дуже корисно.


Якщо ви оголошуєте результат списком <Long>, вам не потрібно використовувати цей утилітний метод. Collectors.toList зробить. Також використання конкретних класів замість інтерфейсів - це кодовий запах.
Lluis Martinez

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

Якщо ви хочете спеціально створити ArrayList, тоді це нормально.
Lluis Martinez

5

Якщо у вас є масив примітивів, ви можете використовувати примітивні колекції, доступні в колекціях Eclipse .

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 (розкриття Я є учасником) має розширення для всіх типів колекції JDK , включаючи 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;

Як і скорочення (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 try com.landawn.abacus.util.stream.LongStreamor LongStreamExin 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 - rian

Тут спершу я перетворив свій 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 :

Виконує операцію скорочення змінних елементів цього потоку за допомогою колектора. Колектор інкапсулює функції, що використовуються в якості аргументів для збору (постачальник, BiConsumer, BiConsumer), дозволяючи повторно використовувати стратегії збору та композицію операцій збору, таких як багаторівневе групування або розподіл. Якщо потік паралельний, а колектор паралельний, або потік не упорядкований, або колектор не упорядкований, тоді буде здійснено одночасне зменшення (детальну інформацію про одночасне зменшення див. У Колекціонері).

Це термінальна операція.

При паралельному виконанні декілька проміжних результатів можуть бути інстанційними, заселеними та об'єднаними, щоб підтримувати ізоляцію змінних структур даних. Тому навіть при виконанні паралельно із структурами даних, що не є безпечними для потоків (наприклад, 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());

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

Дуже дивно дзвонити, collectа потім не зберігати повернене значення. У такому випадку ви можете використовувати forEachзамість цього. Але це все ще невдале рішення.
Лій

1
Використання peek () таким чином є антипаттерном.
Grzegorz Piwowarek

Відповідно до потокового методу Java docs peek метод повинен використовуватися лише для налагодження. Він не повинен використовуватися для будь-якої іншої обробки, крім налагодження.
Vaneet Kataria
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.