Pipeline Sebagai Pengganti Loop
Stream adalah pipeline untuk mengolah urutan nilai. Kamu mulai dari sebuah sumber, biasanya collection, merangkai operasi seperti "simpan yang ini saja", "ubah masing-masing", "urutkan", lalu mengakhirinya dengan mengumpulkan hasilnya. Daripada menulis loop dengan list sementara dan if di dalamnya, kamu menjelaskan apa yang kamu mau, lalu stream yang menelusuri elemennya.
Setelah kamu mengenal lambda, stream jadi mudah dipahami: setiap operasi menerima lambda (atau method reference) yang menjelaskan pekerjaan untuk satu elemen.
Baca dari atas ke bawah: ambil nama-namanya, simpan yang panjang, jadikan huruf besar, kumpulkan ke list. List names yang asli tidak pernah diubah; stream menghasilkan hasil baru dan membiarkan sumbernya apa adanya.
Anatomi Stream
Setiap pipeline punya tiga bagian:
- Sumber:
list.stream(),Arrays.stream(array), atauStream.of(a, b, c). - Nol atau lebih operasi intermediate:
filter,map,sorted,distinct,limit. Masing-masing mengembalikan stream lain, jadi kamu bisa merangkainya. - Tepat satu operasi terminal:
collect,count,forEach,reduce,findFirst. Operasi ini memulai pekerjaannya dan menghasilkan sebuah hasil (atau efek samping).
Pembagian ini penting karena ada satu aturan yang awalnya mengejutkan semua orang.
Operasi Intermediate Bersifat Lazy
Operasi intermediate tidak melakukan apa pun dengan sendirinya. Operasi itu hanya mencatat apa yang seharusnya terjadi. Pipeline baru berjalan saat operasi terminal meminta hasil.
Jalankan kodenya: lambda map tidak pernah dieksekusi. Tambahkan operasi terminal, dan seluruh rangkaian langsung hidup:
Sifat lazy ini bukan keanehan yang perlu dilawan. Justru sifat ini membuat stream bisa menggabungkan operasi dan melewati pekerjaan yang tidak dibutuhkan. Tapi artinya, pipeline tanpa operasi terminal tidak melakukan apa-apa, dan itu bug "kenapa tidak terjadi apa-apa?" yang sering muncul.
filter dan map: Menyimpan dan Mengubah
Dua operasi ini memikul sebagian besar pekerjaan. filter menerima Predicate (lambda yang mengembalikan boolean) dan menyimpan elemen yang lolos. map menerima Function dan mengganti setiap elemen dengan hasil penerapan fungsi itu.
Perhatikan bahwa map bisa mengubah tipe elemen: di sini stream berisi String menjadi stream berisi Integer. String::length adalah method reference, bentuk singkat dari lambda w -> w.length().
Operasi Terminal Menghasilkan Hasil
Setelah stream dibentuk, operasi terminal mengubahnya menjadi sesuatu yang konkret:
Operasi terminal yang umum: collect (mengumpulkan ke list/set/map), count, forEach, anyMatch / allMatch / noneMatch, findFirst, min / max, dan reduce. Setelah operasi terminal berjalan, stream itu sudah terpakai, jadi kamu tidak bisa memakainya ulang. Panggil list.stream() lagi untuk pipeline yang baru.
Mengumpulkan Hasil
collect dengan Collectors adalah andalan untuk membangun hasil. Yang paling umum adalah Collectors.toList():
Collectors juga menyediakan toSet(), joining(...), groupingBy(...), dan counting(). Di Java 16 ke atas, kamu bisa mengganti collect(Collectors.toList()) dengan .toList() yang lebih pendek (hasilnya list yang tidak bisa diubah):
List<String> result = names.stream().map(String::toUpperCase).toList();
sorted, distinct, dan limit
Operasi intermediate ini membentuk ulang stream sebelum kamu mengumpulkannya:
sorted() tanpa argumen memakai urutan alami; berikan Comparator (di sini sebuah lambda) untuk urutan kustom. Comparator.reverseOrder() adalah cara yang lebih rapi untuk mengurutkan secara menurun.
reduce: Melipat Stream Menjadi Satu Nilai
Kalau kamu perlu menggabungkan semua elemen menjadi satu hasil, misalnya jumlah, hasil kali, atau string terpanjang, reduce adalah alat umumnya. Kamu memberinya nilai awal dan fungsi yang menggabungkan dua nilai:
Untuk penjumlahan dan rata-rata biasa, stream khusus lebih jelas: nums.stream().mapToInt(Integer::intValue).sum(). Pakai reduce kalau tidak ada fungsi agregasi yang sudah tersedia.
Stream Tidak Menggantikan Setiap Loop
Stream unggul saat kamu mengubah collection menjadi sebuah hasil. Stream tidak otomatis lebih cepat daripada loop, dan terasa canggung kalau kamu perlu mengubah state di luar atau keluar lebih awal dengan cara yang rumit. Pedoman yang baik: kalau pipeline-nya terbaca sebagai satu kalimat yang jelas, pakai stream; kalau kamu butuh counter bersama atau index, for loop biasa itu jujur dan tidak masalah.
Ingat juga bahwa stream hanya bisa dipakai sekali. Mengoperasikannya dua kali akan melempar exception:
Stream<String> s = names.stream();
s.forEach(System.out::println);
s.count(); // IllegalStateException: stream has already been operated upon
Berikutnya: Optional
Beberapa operasi terminal stream, seperti findFirst, min, max, dan reduce tanpa nilai identitas, mungkin tidak menemukan apa-apa, jadi operasi itu tidak mengembalikan nilai mentah. Hasilnya adalah Optional, wadah Java untuk "mungkin ada nilai, mungkin tidak ada", yang akhirnya memberimu alternatif yang rapi selain mengembalikan null. Itu topik halaman berikutnya.
Pertanyaan yang sering diajukan
Apa itu stream di Java?
Stream adalah pipeline untuk mengolah urutan elemen, biasanya bersumber dari sebuah collection, lewat rangkaian operasi seperti filter, map, dan sorted, yang diakhiri operasi terminal seperti collect atau count. Stream tidak menyimpan data dan tidak mengubah sumbernya; stream menggambarkan sebuah perhitungan. Kamu membuatnya dengan list.stream() dan membaca rangkaiannya dari atas ke bawah seperti resep.
Bagaimana cara mengubah stream kembali menjadi list di Java?
Akhiri pipeline dengan collector terminal: list.stream().filter(...).collect(Collectors.toList()). Di Java 16 ke atas, kamu bisa memakai .toList() yang lebih pendek, yang mengembalikan list yang tidak bisa diubah. Tanpa operasi terminal, tidak ada yang berjalan sama sekali, karena operasi intermediate seperti filter dan map bersifat lazy.
Kapan sebaiknya memakai stream alih-alih for loop?
Pakai stream saat kamu mengubah atau menyaring collection menjadi sebuah hasil, karena pipeline-nya terbaca sebagai satu kalimat yang jelas ("ambil nama-namanya, simpan yang panjang, jadikan huruf besar, kumpulkan ke list"). Tetap pakai for loop biasa kalau kamu perlu mengubah state di luar, keluar lebih awal dengan cara yang rumit, atau logikanya lebih sederhana sebagai langkah-langkah imperatif. Stream soal kejelasan, bukan soal kecepatan mentah.