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:

java SumberNilai.java
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:

text
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:

  1. Baris 9 (Stream.of(...)): tiga String lepas menjadi Stream<String> berisi tiga elemen — cara tercepat menguji pipeline kecil.
  2. Baris 13-14 (Stream.empty()): stream tanpa elemen; toList() menghasilkan List kosong []. Method yang mengembalikan “hasil pencarian kosong” dapat memakai stream kosong alih-alih null.
  3. Baris 17-18 (Arrays.stream(kategori)): array objek menjadi stream elemen sesuai tipe array — di sini Stream<String>.
  4. Baris 21-22 (Arrays.stream(harga).sum()): array int[] menjadi IntStream, spesialisasi primitif yang punya operasi numerik seperti sum(). Total 8000 + 45000 + 85000 = 138000 tercetak tanpa loop.
  5. Baris 25-26 (pelanggan.stream()): sumber paling umum — collection apa pun (List, Set, Deque) punya method stream() 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:

java RentangStok.java
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:

text
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:

  1. 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.
  2. Baris 14 (map(rak -> rak * 10).sum()): rentang 1..8 dipetakan menjadi 10, 20, …, 80 lalu dijumlahkan menjadi 360 — pengganti loop for akumulator klasik tanpa variabel total yang berubah-ubah.
  3. Baris 18 (boxed()): IntStream tidak bisa langsung menjadi List<Integer>; boxed() membungkus tiap int menjadi Integer sehingga toList() 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:

text
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:

  1. Baris 8-9 (iterate(10000, harga -> harga + 5000)): benih 10000 menghasilkan barisan 10000, 15000, 20000, … tanpa akhir. limit(4) memotong menjadi empat elemen pertama sebelum toList() menampungnya.
  2. Baris 12-13 (iterate(5, diskon -> diskon <= 30, diskon -> diskon * 2)): bentuk tiga argumen berhenti sendiri ketika Predicate bernilai false — barisannya 5, 10, 20, lalu berhenti karena 40 melampaui 30. Bentuk ini menggantikan pola iterate(...).limit(n) yang harus menghitung jumlah elemen di muka.
  3. Baris 16-18 (generate + AtomicInteger): Supplier () -> "Pesanan-" + nomor.getAndIncrement() menghasilkan label unik berurutan; AtomicInteger menjaga kebenaran penghitung walau stream nanti dijalankan paralel.
  4. Baris 21-26 (Stream.builder()): add(...) dipanggil berkali-kali — biasanya di dalam loop atau cabang if — lalu build() menyegel hasilnya menjadi Stream<String> tiga elemen.

Sebagai rangkuman, tabel berikut memetakan pabrik stream yang telah kita bahas:

PabrikSumber dataCatatan
Stream.of(nilai...)Nilai lepas / varargsPraktis untuk contoh dan data kecil
Stream.empty()Tidak adaNilai bawaan yang aman, bukan null
Arrays.stream(array)Array objek / primitifint[] langsung menjadi IntStream
collection.stream()List, Set, DequeSumber paling umum di kode aplikasi
IntStream.range(a, b)Rentang angkaBatas atas eksklusif
IntStream.rangeClosed(a, b)Rentang angkaBatas atas inklusif
Stream.iterate(benih, langkah)Komputasi beruntunTak berujung; wajib dipotong
Stream.iterate(benih, syarat, langkah)Komputasi beruntunBerhenti saat Predicate bernilai false
Stream.generate(pemasok)Supplier bebasTak berujung; wajib dipotong
Stream.builder()Perakitan bertahapadd(...) 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 berkas
java
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);
    }
}

StreamGenerator.java

Unduh berkas
java
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());
    }
}

SumberNilai.java

Unduh berkas
java
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());
    }
}