JavaRush /Java Blog /Random-ID /Rehat kopi #177. Panduan Lengkap untuk Java Stream di Jav...

Rehat kopi #177. Panduan Lengkap untuk Java Stream di Java 8

Dipublikasikan di grup Random-ID
Sumber: Hackernoon Postingan ini memberikan tutorial mendetail tentang bekerja dengan Java Stream beserta contoh kode dan penjelasannya. Rehat kopi #177.  Panduan Lengkap untuk Java Stream di Java 8 - 1

Pengantar Java Threads di Java 8

Java Streams, diperkenalkan sebagai bagian dari Java 8, digunakan untuk bekerja dengan kumpulan data. Mereka bukan merupakan struktur data itu sendiri, namun dapat digunakan untuk memasukkan informasi dari struktur data lain dengan mengurutkan dan menyalurkan untuk menghasilkan hasil akhir. Catatan: Penting untuk tidak membingungkan Stream dan Thread, karena dalam bahasa Rusia kedua istilah tersebut sering disebut dalam terjemahan yang sama “aliran”. Stream menunjukkan objek untuk melakukan operasi (paling sering mentransfer atau menyimpan data), sedangkan Thread (terjemahan literal - thread) menunjukkan objek yang memungkinkan kode program tertentu dieksekusi secara paralel dengan cabang kode lainnya. Karena Stream bukan struktur data terpisah, Stream tidak pernah mengubah sumber data. Aliran Java memiliki beberapa fitur berikut:
  1. Java Stream dapat digunakan menggunakan paket “java.util.stream”. Itu dapat diimpor ke skrip menggunakan kode:

    import java.util.stream.* ;

    Dengan menggunakan kode ini, kita juga dapat dengan mudah mengimplementasikan beberapa fungsi bawaan di Java Stream.

  2. Java Stream dapat menerima masukan dari kumpulan data seperti koleksi dan array di Java.

  3. Java Stream tidak memerlukan perubahan struktur data masukan.

  4. Java Stream tidak mengubah sumbernya. Sebaliknya, ini menghasilkan keluaran menggunakan metode saluran pipa yang sesuai.

  5. Java Streams tunduk pada operasi perantara dan terminal, yang akan kita bahas di bagian berikut.

  6. Di Java Stream, operasi perantara disalurkan dan terjadi dalam format evaluasi yang lambat. Mereka diakhiri dengan fungsi terminal. Ini membentuk format dasar untuk menggunakan Java Stream.

Di bagian selanjutnya, kita akan melihat berbagai metode yang digunakan di Java 8 untuk membuat Java Stream jika diperlukan.

Membuat Aliran Java di Java 8

Thread Java dapat dibuat dengan beberapa cara:

1. Membuat aliran kosong menggunakan metode Stream.empty()

Anda dapat membuat aliran kosong untuk digunakan nanti dalam kode Anda. Jika Anda menggunakan metode Stream.empty() , aliran kosong akan dihasilkan, tidak berisi nilai. Aliran kosong ini bisa berguna jika kita ingin melewatkan pengecualian penunjuk nol saat runtime. Untuk melakukan ini, Anda dapat menggunakan perintah berikut:
Stream<String> str = Stream.empty();
Pernyataan di atas akan menghasilkan aliran kosong bernama str tanpa elemen apa pun di dalamnya. Untuk memverifikasi ini, cukup periksa jumlah atau ukuran aliran menggunakan istilah str.count() . Misalnya,
System.out.println(str.count());
Hasilnya, kita mendapatkan 0 pada output .

2. Buat aliran menggunakan metode Stream.builder() dengan instance Stream.Builder

Kita juga dapat menggunakan Stream Builder untuk membuat aliran menggunakan pola desain pembuat. Ini dirancang untuk konstruksi objek selangkah demi selangkah. Mari kita lihat bagaimana kita dapat membuat instance aliran menggunakan Stream Builder .
Stream.Builder<Integer> numBuilder = Stream.builder();

numBuilder.add(1).add(2).add( 3);

Stream<Integer> numStream = numBuilder.build();
Dengan menggunakan kode ini, Anda dapat membuat aliran bernama numStream yang berisi elemen int . Semuanya dilakukan cukup cepat berkat instance Stream.Builder bernama numBuilder yang dibuat terlebih dahulu.

3. Buat aliran dengan nilai yang ditentukan menggunakan metode Stream.of()

Cara lain untuk membuat aliran melibatkan penggunaan metode Stream.of() . Ini adalah cara sederhana untuk membuat aliran dengan nilai tertentu. Ini mendeklarasikan dan juga menginisialisasi thread. Contoh penggunaan metode Stream.of() untuk membuat aliran:
Stream<Integer> numStream = Stream.of(1, 2, 3);
Kode ini akan membuat aliran yang berisi elemen int , seperti yang kita lakukan pada metode sebelumnya menggunakan Stream.Builder . Di sini kita langsung membuat aliran menggunakan Stream.of() dengan nilai yang telah ditentukan sebelumnya [1, 2 dan 3] .

4. Buat aliran dari array yang ada menggunakan metode Arrays.stream()

Metode umum lainnya untuk membuat thread melibatkan penggunaan array di Java. Aliran di sini dibuat dari array yang ada menggunakan metode Arrays.stream() . Semua elemen array diubah menjadi elemen aliran. Berikut ini contoh yang bagus:
Integer[] arr = {1, 2, 3, 4, 5};

Stream<Integer> numStream = Arrays.stream(arr);
Kode ini akan menghasilkan numStream yang berisi konten array bernama arr, yang merupakan array integer.

5. Menggabungkan dua aliran yang ada menggunakan metode Stream.concat()

Metode lain yang dapat digunakan untuk membuat aliran adalah metode Stream.concat() . Ini digunakan untuk menggabungkan dua utas untuk membuat satu utas. Kedua aliran digabungkan secara berurutan. Artinya thread pertama didahulukan, disusul thread kedua, dan seterusnya. Contoh penggabungannya terlihat seperti ini:
Stream<Integer> numStream1 = Stream.of(1, 2, 3, 4, 5);

Stream<Integer> numStream2 = Stream.of(1, 2, 3);

Stream<Integer> combinedStream = Stream.concat( numStream1, numStream2);
Pernyataan di atas akan membuat aliran akhir bernama gabunganStream yang berisi elemen aliran pertama numStream1 dan aliran kedua numStream2 satu per satu .

Jenis operasi dengan Java Stream

Seperti yang telah disebutkan, Anda dapat melakukan dua jenis operasi dengan Java Stream di Java 8: perantara dan terminal. Mari kita lihat masing-masing secara lebih rinci.

Operasi perantara

Operasi perantara menghasilkan aliran keluaran dan dijalankan hanya ketika menghadapi operasi terminal. Ini berarti bahwa operasi perantara dijalankan dengan lambat, tersalurkan, dan hanya dapat diselesaikan oleh operasi terminal. Anda akan belajar tentang evaluasi malas dan pipeline nanti. Contoh operasi perantara adalah metode berikut: filter() , map() , differential() , peek() , sortir() dan beberapa lainnya.

Operasi Terminal

Operasi terminal menyelesaikan pelaksanaan operasi perantara dan juga mengembalikan hasil akhir dari aliran keluaran. Karena operasi terminal menandakan berakhirnya eksekusi lambat dan pipeline, thread ini tidak dapat digunakan lagi setelah menjalani operasi terminal. Contoh operasi terminal adalah metode berikut: forEach() , Collect() , count() , Reduce() dan seterusnya.

Contoh operasi dengan Java Stream

Operasi perantara

Berikut adalah beberapa contoh beberapa operasi perantara yang dapat diterapkan pada Java Stream:

Saring()

Metode ini digunakan untuk memfilter elemen dari aliran yang cocok dengan predikat tertentu di Java. Item yang difilter ini kemudian membentuk aliran baru. Mari kita lihat contoh untuk lebih memahaminya.
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> even = numStream.filter(n -> n % 2 == 0) .collect(Collectors.toList()); System.out.println(even);
Kesimpulan:
[98]
Penjelasan: Dalam contoh ini, Anda dapat melihat bahwa elemen genap (habis dibagi 2) difilter menggunakan metode filter() dan disimpan dalam daftar bilangan bulat numStream , yang isinya akan dicetak nanti. Karena 98 adalah satu-satunya bilangan bulat genap dalam aliran, maka ia dicetak dalam keluaran.

peta()

Metode ini digunakan untuk membuat aliran baru dengan menjalankan fungsi yang dipetakan pada elemen aliran masukan asli. Mungkin aliran baru memiliki tipe data yang berbeda. Contohnya terlihat seperti ini:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> d = numStream.map(n -> n*2) .collect(Collectors.toList()); System.out.println(d);
Kesimpulan:
[86, 130, 2, 196, 126]
Penjelasan: Di sini kita melihat bahwa metode map() digunakan untuk menggandakan setiap elemen aliran numStream . Seperti yang Anda lihat dari keluarannya, setiap elemen dalam aliran telah berhasil digandakan.

berbeda()

Metode ini digunakan untuk mengambil hanya elemen individual dalam aliran dengan memfilter duplikat. Contohnya terlihat seperti ini:
Stream<Integer> numStream = Stream.of(43,65,1,98,63,63,1); List<Integer> numList = numStream.distinct() .collect(Collectors.toList()); System.out.println(numList);
Kesimpulan:
[43, 65, 1, 98, 63]
Penjelasan: Dalam hal ini, metode Different() digunakan untuk numStream . Ia mengambil semua elemen individual di numList dengan menghapus duplikat dari aliran. Seperti yang Anda lihat dari output, tidak ada duplikat, tidak seperti aliran input, yang awalnya memiliki dua duplikat (63 dan 1).

mengintip()

Metode ini digunakan untuk melacak perubahan antara sebelum menjalankan operasi terminal. Ini berarti peek() dapat digunakan untuk melakukan operasi pada setiap elemen aliran untuk membuat aliran di mana operasi perantara lebih lanjut dapat dilakukan.
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> nList = numStream.map(n -> n*10) .peek(n->System.out.println("Mapped: "+ n)) .collect(Collectors.toList()); System.out.println(nList);
Kesimpulan:
Dipetakan: 430 Dipetakan: 650 Dipetakan: 10 Dipetakan: 980 Dipetakan: 630 [430, 650, 10, 980, 630]
Penjelasan: Di sini metode peek() digunakan untuk menghasilkan hasil antara saat metode map() diterapkan pada elemen aliran. Di sini kita dapat melihat bahwa bahkan sebelum menggunakan operasi terminal Collect() untuk mencetak konten akhir daftar dalam pernyataan print , hasil untuk setiap pemetaan elemen aliran dicetak secara berurutan terlebih dahulu.

diurutkan()

Metode sortir() digunakan untuk mengurutkan elemen aliran. Secara default, ini mengurutkan elemen dalam urutan menaik. Anda juga dapat menentukan urutan tertentu sebagai parameter. Contoh penerapan metode ini terlihat seperti ini:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); numStream.sorted().forEach(n -> System.out.println(n));
Kesimpulan:
1 43 ​​​​63 65 98
Penjelasan: Di sini, metode sortir() digunakan untuk mengurutkan elemen aliran dalam urutan menaik secara default (karena tidak ada urutan khusus yang ditentukan). Anda dapat melihat bahwa item yang dicetak pada output disusun dalam urutan menaik.

Operasi Terminal

Berikut beberapa contoh beberapa operasi terminal yang dapat diterapkan pada aliran Java:

untuk setiap()

Metode forEach() digunakan untuk mengulangi semua elemen aliran dan menjalankan fungsi pada setiap elemen satu per satu. Ini bertindak sebagai alternatif untuk pernyataan loop seperti for , while dan lain-lain. Contoh:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); numStream.forEach(n -> System.out.println(n));
Kesimpulan:
43 65 1 98 63
Penjelasan: Di sini metode forEach() digunakan untuk mencetak setiap elemen aliran satu per satu.

menghitung()

Metode count() digunakan untuk mengambil jumlah total elemen yang ada di aliran. Ini mirip dengan metode size() , yang sering digunakan untuk menentukan jumlah total elemen dalam suatu koleksi. Contoh penggunaan metode count() dengan Java Stream adalah sebagai berikut:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); System.out.println(numStream.count());
Kesimpulan:
5
Penjelasan: Karena numStream berisi 5 elemen bilangan bulat, penggunaan metode count() akan menghasilkan 5.

mengumpulkan()

Metode Collect() digunakan untuk melakukan reduksi elemen aliran yang dapat diubah. Ini dapat digunakan untuk menghapus konten dari aliran setelah pemrosesan selesai. Ia menggunakan kelas Collector untuk melakukan reduksi .
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> odd = numStream.filter(n -> n % 2 == 1) .collect(Collectors.toList()); System.out.println(odd);
Kesimpulan:
[43, 65, 1, 63]
Penjelasan: Dalam contoh ini, semua elemen ganjil dalam aliran difilter dan dikumpulkan/dikurangi ke dalam daftar bernama ganjil . Pada akhirnya, daftar ganjil dicetak.

min() dan maks()

Metode min() , seperti namanya, dapat digunakan pada aliran untuk menemukan elemen minimum di dalamnya. Demikian pula, metode max() dapat digunakan untuk menemukan elemen maksimum dalam suatu aliran. Mari kita coba memahami bagaimana mereka dapat digunakan dengan sebuah contoh:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); int smallest = numStream.min((m, n) -> Integer.compare(m, n)).get(); System.out.println("Smallest element: " + smallest);
numStream = Stream.of(43, 65, 1, 98, 63); int largest = numStream.max((m, n) -> Integer.compare(m, n)).get(); System.out.println("Largest element: " + largest);
Kesimpulan:
Unsur terkecil : 1 Unsur terbesar : 98
Penjelasan: Dalam contoh ini, kami mencetak elemen terkecil di numStream menggunakan metode min() dan elemen terbesar menggunakan metode max() . Perhatikan bahwa di sini, sebelum menggunakan metode max() , kami telah menambahkan elemen ke numStream lagi . Ini karena min() adalah operasi terminal dan menghancurkan konten aliran asli, hanya mengembalikan hasil akhir (yang dalam hal ini adalah bilangan bulat “terkecil”).

findAny() dan findFirst()

findAny() mengembalikan elemen aliran apa pun sebagai Optional . Jika alirannya kosong, ia juga akan mengembalikan Optional value , yang akan kosong. findFirst() mengembalikan elemen pertama aliran sebagai Opsional . Seperti halnya metode findAny() , metode findFirst() juga mengembalikan parameter Opsional kosong jika aliran terkait kosong. Mari kita lihat contoh berikut berdasarkan metode ini:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); Optional<Integer> opt = numStream.findFirst();System.out.println(opt); numStream = Stream.empty(); opt = numStream.findAny();System.out.println(opt);
Kesimpulan:
Opsional[43] Opsional.kosong
Penjelasan: Di sini, dalam kasus pertama, metode findFirst() mengembalikan elemen pertama aliran sebagai Optional . Lalu, saat thread ditetapkan ulang sebagai thread kosong, metode findAny() akan mengembalikan Optional yang kosong .

allMatch() , anyMatch() dan noneMatch()

Metode allMatch() digunakan untuk memeriksa apakah semua elemen dalam aliran cocok dengan predikat tertentu dan mengembalikan nilai boolean true jika cocok, jika tidak maka akan mengembalikan false . Jika alirannya kosong, ia mengembalikan true . Metode anyMatch() digunakan untuk memeriksa apakah salah satu elemen dalam aliran cocok dengan predikat tertentu. Ia mengembalikan nilai benar jika demikian, salah jika sebaliknya. Jika alirannya kosong, ia mengembalikan false . Metode noneMatch() mengembalikan nilai true jika tidak ada elemen dalam aliran yang cocok dengan predikatnya, dan false jika tidak. Contoh untuk mengilustrasikannya terlihat seperti ini:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); boolean flag = numStream.allMatch(n -> n1); System.out.println(flag); numStream = Stream.of(43, 65, 1, 98, 63); flag = numStream.anyMatch(n -> n1); System.out.println(flag); numStream = Stream.of(43, 65, 1, 98, 63); flag = numStream.noneMatch(n -> n==1);System.out.println(flag);
Kesimpulan:
salah benar salah
Penjelasan: Untuk aliran numStream yang berisi 1 sebagai elemen, metode allMatch() mengembalikan false karena semua elemen bukan 1, tetapi hanya satu yang bernilai. Metode anyMatch() mengembalikan nilai benar karena setidaknya salah satu elemennya adalah 1. Metode noneMatch() mengembalikan nilai salah karena 1 benar-benar ada sebagai elemen dalam aliran ini.

Evaluasi Malas di Java Stream

Evaluasi yang lambat mengarah pada pengoptimalan saat bekerja dengan Java Streams di Java 8. Evaluasi ini terutama melibatkan penundaan operasi perantara hingga operasi terminal ditemukan. Evaluasi yang malas bertanggung jawab untuk mencegah pemborosan sumber daya yang tidak perlu dalam perhitungan hingga hasilnya benar-benar dibutuhkan. Aliran keluaran yang dihasilkan dari operasi perantara dihasilkan hanya setelah operasi terminal selesai. Evaluasi malas berfungsi dengan semua operasi perantara di aliran Java. Penggunaan evaluasi malas yang sangat berguna terjadi ketika bekerja dengan aliran tak terbatas. Dengan cara ini, banyak pemrosesan yang tidak perlu dapat dicegah.

Saluran pipa di Java Stream

Sebuah pipeline di Java Stream terdiri dari aliran input, nol atau lebih operasi perantara yang berbaris satu demi satu, dan terakhir operasi terminal. Operasi perantara di Java Streams dilakukan dengan lambat. Hal ini membuat operasi perantara melalui pipa tidak dapat dihindari. Dengan pipeline, yang pada dasarnya merupakan operasi perantara yang digabungkan secara berurutan, eksekusi yang lambat menjadi mungkin. Saluran pipa membantu melacak operasi perantara yang perlu dilakukan setelah operasi terminal akhirnya ditemui.

Kesimpulan

Mari kita rangkum apa yang telah kita pelajari hari ini. Dalam artikel ini:
  1. Kami melihat sekilas apa itu Java Streams.
  2. Kami kemudian mempelajari banyak teknik berbeda untuk membuat thread Java di Java 8.
  3. Kita mempelajari dua jenis operasi utama (operasi perantara dan operasi terminal) yang dapat dilakukan pada aliran Java.
  4. Kami kemudian melihat secara rinci beberapa contoh operasi perantara dan terminal.
  5. Kami akhirnya belajar lebih banyak tentang evaluasi malas dan akhirnya belajar tentang pipelining di thread Java.
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION