Setiap pipeline dimulai dari sumber data, dan Java menyediakan banyak pintu masuk untuk menciptakannya. Kadang data sudah tersimpan di List atau array; kadang kita hanya punya dua-tiga nilai lepas; kadang pula data harus dihitung begitu saja — rentang nomor rak, barisan kenaikan harga, atau nomor pesanan yang bertambah terus. Pada Chapter ini kita akan menyoroti satu per satu pabrik stream: Stream.of, Arrays.stream, collection.stream(), Stream.empty, IntStream.range, iterate, generate, sampai Stream.Builder, lalu mencatat kapan masing-masing layak dipilih.
Stream dari Nilai, Array, dan Collection #
Tiga cara pertama menutupi sebagian besar kebutuhan sehari-hari. Stream.of menerima sejumlah nilai lepas (varargs), Arrays.stream membungkus array — termasuk array primitif yang langsung menjadi IntStream — dan collection.stream() mengalirkan isi List atau Set. Stream.empty() menghasilkan stream kosong; kelihatannya sepele, tetapi stream kosong berguna sebagai nilai bawaan saat method harus mengembalikan stream walau tidak ada data. Program berikut mendemonstrasikan kelimanya:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class SumberNilai {
public static void main(String[] args) {
// 1. Stream.of: dari sejumlah nilai lepas (varargs)
Stream<String> promo = Stream.of("Diskon 10%", "Gratis ongkir", "Cashback 5%");
System.out.println("Stream.of : " + promo.toList());
// 2. Stream.empty: stream kosong, berguna sebagai nilai bawaan
Stream<String> kosong = Stream.empty();
System.out.println("Stream.empty : " + kosong.toList());
// 3. Arrays.stream: dari array objek
String[] kategori = {"makanan", "minuman", "alat tulis"};
System.out.println("Arrays.stream : " + Arrays.stream(kategori).toList());
// 4. Arrays.stream: dari array primitif menjadi IntStream
int[] harga = {8000, 45000, 85000};
System.out.println("Total harga : " + Arrays.stream(harga).sum());
// 5. collection.stream(): sumber paling sering dipakai sehari-hari
List<String> pelanggan = List.of("Budi", "Siti", "Dewi");
System.out.println("List.stream : " + pelanggan.stream().toList());
}
}Output yang Dihasilkan:
Stream.of : [Diskon 10%, Gratis ongkir, Cashback 5%]
Stream.empty : []
Arrays.stream : [makanan, minuman, alat tulis]
Total harga : 138000
List.stream : [Budi, Siti, Dewi]Mari kita bedah sumber-sumber tersebut:
- Baris 9 (
Stream.of(...)): tigaStringlepas menjadiStream<String>berisi tiga elemen — cara tercepat menguji pipeline kecil. - Baris 13-14 (
Stream.empty()): stream tanpa elemen;toList()menghasilkanListkosong[]. Method yang mengembalikan “hasil pencarian kosong” dapat memakai stream kosong alih-alihnull. - Baris 17-18 (
Arrays.stream(kategori)): array objek menjadi stream elemen sesuai tipe array — di siniStream<String>. - Baris 21-22 (
Arrays.stream(harga).sum()): arrayint[]menjadiIntStream, spesialisasi primitif yang punya operasi numerik sepertisum(). Total 8000 + 45000 + 85000 = 138000 tercetak tanpa loop. - Baris 25-26 (
pelanggan.stream()): sumber paling umum — collection apa pun (List,Set,Deque) punya methodstream()yang membaca isinya berurutan.
Rentang Angka dengan IntStream #
Untuk data numerik berpola, IntStream menyediakan pabrik rentang. range(a, b) menghasilkan angka dari a sampai b-1 (batas atas eksklusif), sedangkan rangeClosed(a, b) memuat b juga (inklusif). Keduanya bekerja mulus dengan operasi antara seperti map, dan boxed() mengangkat IntStream menjadi Stream<Integer> saat kita membutuhkan tipe umum:
import java.util.List;
import java.util.stream.IntStream;
public class RentangStok {
public static void main(String[] args) {
// range: batas atas EKSKLUSIF -- 6 tidak ikut
System.out.println("range(1, 6) : " + IntStream.range(1, 6).boxed().toList());
// rangeClosed: batas atas INKLUSIF -- 5 ikut
System.out.println("rangeClosed(1, 5): " + IntStream.rangeClosed(1, 5).boxed().toList());
// Tiap rak menampung rak*10 unit; total untuk rak 1..8
int totalStok = IntStream.rangeClosed(1, 8).map(rak -> rak * 10).sum();
System.out.println("Total stok : " + totalStok);
// boxed(): membungkus IntStream menjadi Stream<Integer>
List<Integer> kupon = IntStream.rangeClosed(5, 7).boxed().toList();
System.out.println("Kupon : " + kupon);
}
}Output yang Dihasilkan:
range(1, 6) : [1, 2, 3, 4, 5]
rangeClosed(1, 5): [1, 2, 3, 4, 5]
Total stok : 360
Kupon : [5, 6, 7]Bedah singkatnya sebagai berikut:
- Baris 8 dan 11: kedua pemanggilan mencetak
[1, 2, 3, 4, 5]— tetapi lewat jalur berbeda:range(1, 6)berhenti sebelum 6,rangeClosed(1, 5)memuat 5. Pilih salah satu sesuai konvensi batas yang kita inginkan, lalu konsisten. - Baris 14 (
map(rak -> rak * 10).sum()): rentang 1..8 dipetakan menjadi 10, 20, …, 80 lalu dijumlahkan menjadi 360 — pengganti loopforakumulator klasik tanpa variabeltotalyang berubah-ubah. - Baris 18 (
boxed()):IntStreamtidak bisa langsung menjadiList<Integer>;boxed()membungkus tiapintmenjadiIntegersehinggatoList()bisa menampungnya.
IntStream, LongStream, dan DoubleStream adalah spesialisasi primitif dari stream — kita akan menggarapnya tuntas pada Chapter Primitive Streams.
Stream Tak Berujung dan Builder #
Pabrik berikutnya menghasilkan data secara komputasional. Stream.iterate(benih, langkah) menghasilkan nilai pertama dari benih, lalu tiap nilai berikutnya dihitung dari nilai sebelumnya. Stream.generate(pemasok) memanggil Supplier untuk tiap elemen tanpa peduli nilai sebelumnya. Keduanya tak berujung — elemennya baru berhenti kalau kita memotongnya dengan limit atau operasi akhir yang berhenti dini. Sejak Java 9, iterate juga menerima bentuk tiga argumen dengan Predicate sebagai syarat lanjut, sehingga batas berhenti bisa dinyatakan langsung di pabriknya:
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
public class StreamGenerator {
public static void main(String[] args) {
// iterate dua argumen: nilai benih + fungsi langkah berikutnya
Stream<Integer> kenaikanHarga = Stream.iterate(10000, harga -> harga + 5000);
System.out.println("iterate 2-arg : " + kenaikanHarga.limit(4).toList());
// iterate tiga argumen (Java 9+): benih + syarat lanjut + fungsi langkah
Stream<Integer> tanggaDiskon = Stream.iterate(5, diskon -> diskon <= 30, diskon -> diskon * 2);
System.out.println("iterate 3-arg : " + tanggaDiskon.toList());
// generate: Supplier bebas yang tidak melihat nilai sebelumnya
AtomicInteger nomor = new AtomicInteger(1);
Stream<String> pesanan = Stream.generate(() -> "Pesanan-" + nomor.getAndIncrement());
System.out.println("generate : " + pesanan.limit(3).toList());
// builder: merakit stream tahap demi tahap, lalu build()
Stream<String> menu = Stream.<String>builder()
.add("Kopi Susu")
.add("Teh Tarik")
.add("Roti Bakar")
.build();
System.out.println("builder : " + menu.toList());
}
}Output yang Dihasilkan:
iterate 2-arg : [10000, 15000, 20000, 25000]
iterate 3-arg : [5, 10, 20]
generate : [Pesanan-1, Pesanan-2, Pesanan-3]
builder : [Kopi Susu, Teh Tarik, Roti Bakar]Mari kita amati tiap pabrik di atas:
- Baris 8-9 (
iterate(10000, harga -> harga + 5000)): benih 10000 menghasilkan barisan 10000, 15000, 20000, … tanpa akhir.limit(4)memotong menjadi empat elemen pertama sebelumtoList()menampungnya. - Baris 12-13 (
iterate(5, diskon -> diskon <= 30, diskon -> diskon * 2)): bentuk tiga argumen berhenti sendiri ketikaPredicatebernilaifalse— barisannya 5, 10, 20, lalu berhenti karena 40 melampaui 30. Bentuk ini menggantikan polaiterate(...).limit(n)yang harus menghitung jumlah elemen di muka. - Baris 16-18 (
generate+AtomicInteger):Supplier() -> "Pesanan-" + nomor.getAndIncrement()menghasilkan label unik berurutan;AtomicIntegermenjaga kebenaran penghitung walau stream nanti dijalankan paralel. - Baris 21-26 (
Stream.builder()):add(...)dipanggil berkali-kali — biasanya di dalam loop atau cabangif— lalubuild()menyegel hasilnya menjadiStream<String>tiga elemen.
Sebagai rangkuman, tabel berikut memetakan pabrik stream yang telah kita bahas:
| Pabrik | Sumber data | Catatan |
|---|---|---|
Stream.of(nilai...) | Nilai lepas / varargs | Praktis untuk contoh dan data kecil |
Stream.empty() | Tidak ada | Nilai bawaan yang aman, bukan null |
Arrays.stream(array) | Array objek / primitif | int[] langsung menjadi IntStream |
collection.stream() | List, Set, Deque | Sumber paling umum di kode aplikasi |
IntStream.range(a, b) | Rentang angka | Batas atas eksklusif |
IntStream.rangeClosed(a, b) | Rentang angka | Batas atas inklusif |
Stream.iterate(benih, langkah) | Komputasi beruntun | Tak berujung; wajib dipotong |
Stream.iterate(benih, syarat, langkah) | Komputasi beruntun | Berhenti saat Predicate bernilai false |
Stream.generate(pemasok) | Supplier bebas | Tak berujung; wajib dipotong |
Stream.builder() | Perakitan bertahap | add(...) lalu build() |
Kita kini bisa menciptakan stream dari hampir semua sumber. Pertanyaan berikutnya: kapan elemen benar-benar diproses? Chapter Intermediate vs Terminal Operations membedah pembagian dua jenis operasi — yang menyusun pipeline dan yang mengeksekusinya — lengkap dengan bukti nyata bahwa pipeline tanpa operasi akhir tidak melakukan apa pun.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
RentangStok.java
Unduh berkasimport java.util.List;
import java.util.stream.IntStream;
public class RentangStok {
public static void main(String[] args) {
// range: batas atas EKSKLUSIF -- 6 tidak ikut
System.out.println("range(1, 6) : " + IntStream.range(1, 6).boxed().toList());
// rangeClosed: batas atas INKLUSIF -- 5 ikut
System.out.println("rangeClosed(1, 5): " + IntStream.rangeClosed(1, 5).boxed().toList());
// Tiap rak menampung rak*10 unit; total untuk rak 1..8
int totalStok = IntStream.rangeClosed(1, 8).map(rak -> rak * 10).sum();
System.out.println("Total stok : " + totalStok);
// boxed(): membungkus IntStream menjadi Stream<Integer>
List<Integer> kupon = IntStream.rangeClosed(5, 7).boxed().toList();
System.out.println("Kupon : " + kupon);
}
}
StreamGenerator.java
Unduh berkasimport java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
public class StreamGenerator {
public static void main(String[] args) {
// iterate dua argumen: nilai benih + fungsi langkah berikutnya
Stream<Integer> kenaikanHarga = Stream.iterate(10000, harga -> harga + 5000);
System.out.println("iterate 2-arg : " + kenaikanHarga.limit(4).toList());
// iterate tiga argumen (Java 9+): benih + syarat lanjut + fungsi langkah
Stream<Integer> tanggaDiskon = Stream.iterate(5, diskon -> diskon <= 30, diskon -> diskon * 2);
System.out.println("iterate 3-arg : " + tanggaDiskon.toList());
// generate: Supplier bebas yang tidak melihat nilai sebelumnya
AtomicInteger nomor = new AtomicInteger(1);
Stream<String> pesanan = Stream.generate(() -> "Pesanan-" + nomor.getAndIncrement());
System.out.println("generate : " + pesanan.limit(3).toList());
// builder: merakit stream tahap demi tahap, lalu build()
Stream<String> menu = Stream.<String>builder()
.add("Kopi Susu")
.add("Teh Tarik")
.add("Roti Bakar")
.build();
System.out.println("builder : " + menu.toList());
}
}
SumberNilai.java
Unduh berkasimport java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class SumberNilai {
public static void main(String[] args) {
// 1. Stream.of: dari sejumlah nilai lepas (varargs)
Stream<String> promo = Stream.of("Diskon 10%", "Gratis ongkir", "Cashback 5%");
System.out.println("Stream.of : " + promo.toList());
// 2. Stream.empty: stream kosong, berguna sebagai nilai bawaan
Stream<String> kosong = Stream.empty();
System.out.println("Stream.empty : " + kosong.toList());
// 3. Arrays.stream: dari array objek
String[] kategori = {"makanan", "minuman", "alat tulis"};
System.out.println("Arrays.stream : " + Arrays.stream(kategori).toList());
// 4. Arrays.stream: dari array primitif menjadi IntStream
int[] harga = {8000, 45000, 85000};
System.out.println("Total harga : " + Arrays.stream(harga).sum());
// 5. collection.stream(): sumber paling sering dipakai sehari-hari
List<String> pelanggan = List.of("Budi", "Siti", "Dewi");
System.out.println("List.stream : " + pelanggan.stream().toList());
}
}