Mengambil Daftar dari java.util.stream.Stream di Java 8

443

Saya sedang bermain-main dengan Java 8 lambdas untuk dengan mudah menyaring koleksi. Tetapi saya tidak menemukan cara ringkas untuk mengambil hasilnya sebagai daftar baru dalam pernyataan yang sama. Inilah pendekatan saya yang paling ringkas sejauh ini:

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);

Contoh di internet tidak menjawab pertanyaan saya karena berhenti tanpa menghasilkan daftar hasil baru. Pasti ada cara yang lebih ringkas. Saya akan diharapkan, bahwa Streamkelas memiliki metode seperti toList(), toSet()...

Apakah ada cara agar variabel targetLongListdapat langsung ditugaskan oleh baris ketiga?

Daniel K.
sumber
7
Jika Anda tidak perlu sourceLongListsetelah itu ada Collection.removeIf(…)untuk kenyamanan.
Holger
2
Bagaimana dengan ini? List<Long> targetLongList = sourceLongList.stream().collect(Collectors.toList());
leeyuiwah

Jawaban:

609

Apa yang Anda lakukan mungkin merupakan cara paling sederhana, asalkan aliran Anda tetap berurutan — jika tidak, Anda harus melakukan panggilan ke sekuensial () sebelumnya forEach.

[sunting kemudian: alasan pemanggilan sekuensial () diperlukan adalah karena kode yang ada ( forEach(targetLongList::add)) akan bersemangat jika alirannya paralel. Bahkan kemudian, itu tidak akan mencapai efek yang dimaksudkan, seperti forEachyang secara eksplisit tidak ditentukan - bahkan dalam aliran berurutan urutan pemrosesan elemen tidak dijamin. Anda harus menggunakan forEachOrdereduntuk memastikan pemesanan yang benar. Maksud dari perancang Stream API adalah Anda akan menggunakan kolektor dalam situasi ini, seperti di bawah ini.]

Alternatifnya adalah

targetLongList = sourceLongList.stream()
    .filter(l -> l > 100)
    .collect(Collectors.toList());
Maurice Naftalin
sumber
10
Tambahan: Saya pikir kode ini menjadi sedikit lebih pendek, lebih jelas dan lebih cantik jika Anda menggunakan impor statis toList. Hal ini dilakukan dengan menempatkan berikut di antara impor file: static import java.util.stream.Collectors.toList;. Kemudian panggilan kumpulkan hanya berbunyi .collect(toList()).
Lii
4
Dalam Eclipse dimungkinkan untuk membuat IDE menambahkan impor statis untuk metode. Ini dilakukan dengan menambahkan Collectorskelas di Preferensi -> Java -> Editor -> Content Assist -> Favorites . Setelah ini, Anda hanya perlu mengetik toLidi tekan Ctr + Space untuk mengisi IDE toListdan menambahkan impor statis.
Lii
3
Satu hal yang perlu diingat adalah bahwa IntStreamdan beberapa lainnya hampir-tetapi-tidak- Streamcukup tidak memiliki collect(Collector)metode dan Anda harus menelepon IntStream.boxed()untuk mengubahnya menjadi biasa Streamterlebih dahulu. Kemudian lagi, mungkin Anda hanya ingin toArray().
Mutant Bob
mengapa kita harus menggunakan sequential()sebelum forEachatau menggunakan 'forEachOrdered`
amarnath harish
1
@amarnathharish Karena forEach tidak menjamin urutan pelaksanaan operasi untuk aliran paralel. JavaDoc mengatakan "Perilaku operasi ini secara eksplisit tidak deterministik. Untuk pipa aliran paralel, operasi ini tidak menjamin untuk menghormati urutan pertemuan arus, karena dengan melakukan itu akan mengorbankan manfaat paralelisme." (Kalimat pertama dari kutipan ini sebenarnya berarti bahwa pesanan juga tidak dijamin untuk aliran berurutan, meskipun dalam praktiknya dipertahankan.)
Maurice Naftalin
183

Diperbarui:

Pendekatan lain adalah menggunakan Collectors.toList:

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

Solusi Sebelumnya:

Pendekatan lain adalah menggunakan Collectors.toCollection:

targetLongList = 
    sourceLongList.stream().
    filter(l -> l > 100).
    collect(Collectors.toCollection(ArrayList::new));
Mohamed Sanaulla
sumber
60
Namun, ini berguna jika Anda menginginkan implementasi Daftar tertentu.
orbfish
1
Meskipun direkomendasikan untuk melakukan kode terhadap antarmuka, ada kasus yang jelas (salah satunya adalah GWT) ketika Anda harus kode terhadap implementasi konkret (kecuali jika Anda ingin semua implementasi Daftar dikompilasi dan dikirim sebagai javascript).
Eduard Korenschi
3
Pro lain untuk metode ini, dari Collectors::toListjavadoc: "Tidak ada jaminan pada jenis, mutabilitas, serializability, atau keamanan thread dari Daftar yang dikembalikan; jika diperlukan kontrol lebih besar terhadap Daftar yang dikembalikan, gunakan toCollection(Supplier)."
Charles Wood
12

Saya suka menggunakan metode util yang mengembalikan kolektor untuk ArrayList ketika itulah yang saya inginkan.

Saya pikir solusinya menggunakan Collectors.toCollection(ArrayList::new) agak terlalu berisik untuk operasi yang umum.

Contoh:

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

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

Dengan jawaban ini saya juga ingin menunjukkan betapa sederhananya membuat dan menggunakan kolektor khusus, yang umumnya sangat berguna.

Lii
sumber
Jika Anda mendeklarasikan hasil sebagai Daftar <Panjang> Anda tidak perlu menggunakan metode util ini. Collectors.toList akan melakukannya. Juga, menggunakan kelas-kelas tertentu bukan antarmuka adalah bau kode.
Lluis Martinez
1
@LluisMartinez: "Collectors.toList akan melakukannya." : Tidak, tidak dalam banyak situasi. Karena itu bukan ide yang baik untuk digunakan toListjika Anda misalnya ingin mengubah daftar nanti di program. The toListdokumentasi mengatakan ini: "Tidak ada jaminan pada jenis, berubah-ubah, serializability, atau benang-keselamatan Daftar kembali, jika lebih kontrol atas Daftar kembali diperlukan, penggunaan toCollection." . Jawaban saya menunjukkan cara untuk membuatnya lebih mudah untuk melakukan itu dalam kasus umum.
Lii
Jika Anda ingin secara khusus membuat ArrayList maka tidak apa-apa.
Lluis Martinez
5

Jika Anda memiliki larik primitif, Anda dapat menggunakan koleksi primitif yang tersedia di Eclipse Collections .

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

Jika Anda tidak dapat mengubah sourceLongList dari 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<>());

Jika Anda ingin menggunakan 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);

Catatan: Saya adalah kontributor untuk Eclipse Collections.

Nikhil Nanivadekar
sumber
4

Cara yang sedikit lebih efisien (hindari pembuatan Daftar sumber dan buka otomatis oleh filter):

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

Jika Anda tidak keberatan menggunakan perpustakaan pihak ketiga, cyclop-react lib (pengungkapan Saya adalah kontributor) AOL memiliki ekstensi untuk semua jenis Koleksi JDK , termasuk Daftar . Antarmuka ListX memperluas java.util.List dan menambahkan sejumlah besar operator yang berguna, termasuk filter.

Anda cukup menulis-

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

ListX juga dapat dibuat dari Daftar yang ada (via ListX.fromIterable)

John McClean
sumber
1

Ada varian lain dari metode kumpulkan yang disediakan oleh kelas LongStream dan juga oleh kelas IntStream dan DoubleStream.

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

Melakukan operasi pengurangan yang bisa berubah pada elemen aliran ini. Pengurangan yang bisa berubah adalah di mana nilai yang dikurangi adalah wadah hasil yang bisa berubah, seperti ArrayList, dan elemen digabungkan dengan memperbarui status hasil alih-alih dengan mengganti hasilnya. Ini menghasilkan hasil yang setara dengan:

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

Seperti mengurangi (panjang, LongBinaryOperator), operasi pengumpulan dapat diparalelkan tanpa memerlukan sinkronisasi tambahan. Ini adalah operasi terminal.

Dan jawaban untuk pertanyaan Anda dengan metode kumpulkan ini adalah sebagai berikut:

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

Di bawah ini adalah varian referensi metode yang cukup pintar tetapi beberapa yang sulit dipahami:

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

Di bawah ini akan menjadi varian HashSet:

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

Varian LinkedList serupa adalah seperti ini:

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

Dalam kasus seseorang (seperti saya) di luar sana sedang mencari cara berurusan dengan Objek, bukan tipe primitif kemudian gunakan 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);

cetakan:

List type: class java.util.ArrayList
Elem type: class java.lang.Character
An alternative way is to insert the following VM o
Kashyap
sumber
0
String joined = 
                Stream.of(isRead?"read":"", isFlagged?"flagged":"", isActionRequired?"action":"", isHide?"hide":"")
                      .filter(s -> s != null && !s.isEmpty())
                      .collect(Collectors.joining(","));
Sharar
sumber
0

Berikut ini kode dari AbacusUtil

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

Pengungkapan : Saya pengembang AbacusUtil.

user_3380739
sumber
Saya tidak menemukan metode toList yang ada di kelas LongStream. Bisakah Anda menjalankan kode ini?
Vaneet Kataria
@VaneetKataria coba com.landawn.abacus.util.stream.LongStreamatau LongStreamExdi AbacusUtil
user_3380739
0

Anda dapat menulis ulang kode seperti di bawah ini:

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());
Pratik Pawar
sumber
Terima kasih atas masukan Anda. Namun, tolong jelaskan apa yang Anda ubah dan sejauh mana itu terkait dengan pertanyaan.
B - rian
Di sini pertama-tama saya mengonversi ArrayList ke uap menggunakan filter. Saya menyaring data yang diperlukan. Akhirnya saya telah menggunakan metode pengumpulan aliran java 8 untuk mengumpulkan data dalam daftar baru yang disebut sebagai targetLongList.
Pratik Pawar
0

Untuk mengumpulkan dalam daftar yang dapat diubah:

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

Untuk mengumpulkan dalam daftar yang tidak berubah:

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

Penjelasan collectdari javadoc :

Melakukan operasi pengurangan yang bisa berubah pada elemen aliran ini menggunakan Kolektor. Seorang Kolektor merangkum fungsi yang digunakan sebagai argumen untuk mengumpulkan (Pemasok, BiConsumer, BiConsumer), memungkinkan untuk menggunakan kembali strategi pengumpulan dan komposisi operasi pengumpulan seperti pengelompokan atau partisi multi-level. Jika aliran paralel, dan Kolektor bersamaan, dan aliran tidak berurutan atau kolektor tidak berurutan, maka pengurangan bersamaan akan dilakukan (lihat Kolektor untuk detail tentang reduksi bersamaan.)

Ini adalah operasi terminal.

Ketika dieksekusi secara paralel, beberapa hasil antara dapat dipakai, diisi, dan digabungkan untuk menjaga isolasi struktur data yang bisa berubah. Oleh karena itu, bahkan ketika dieksekusi secara paralel dengan struktur data yang tidak aman (seperti ArrayList), tidak diperlukan sinkronisasi tambahan untuk pengurangan paralel.

Saikat
sumber
-3

Jika Anda tidak menggunakan parallel()ini akan berhasil

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());
Debapriya Biswas
sumber
Saya tidak suka bahwa collect () hanya digunakan untuk menggerakkan aliran sehingga kait mengintip () dipanggil pada setiap item. Hasil operasi terminal dibuang.
Daniel K.
Sangat aneh untuk menelepon collectdan kemudian tidak menyimpan nilai kembali. Dalam hal ini Anda dapat menggunakannya forEachsebagai gantinya. Tapi itu masih solusi yang buruk.
Lii
1
Menggunakan peek () dengan cara ini adalah antipattern.
Grzegorz Piwowarek
Sesuai Stream, metode Java docs peek harus digunakan untuk tujuan debugging saja. Metode ini tidak boleh digunakan untuk pemrosesan selain dari debugging.
Vaneet Kataria