Tidak semua pipeline harus memroses seluruh data. Antarmuka katalog menampilkan 20 produk per halaman, pratinjau laporan cukup sepuluh baris pertama, dan kadang kita hanya ingin “semua item sampai ketemu yang kosong stoknya”. Stream API menyediakan empat operasi pemotong: limit dan skip memotong berdasarkan jumlah, sedangkan takeWhile dan dropWhile — hadir sejak Java 9 — memotong berdasarkan kondisi yang hanya berlaku di awal aliran. Pada Chapter ini kita akan membangun pagination dengan limit + skip, lalu membuktikan lewat keluaran nyata bahwa takeWhile berperilaku berbeda dari filter.

Memotong Jumlah Elemen dengan limit dan skip #

limit(n) meloloskan maksimal n elemen pertama lalu memotong aliran, sedangkan skip(n) membuang n elemen pertama dan meloloskan sisanya. Keduanya sering dipasangkan untuk pola pagination: skip melompati halaman-halaman sebelumnya, limit menahan ukuran halaman. Contoh berikut membungkus pola tersebut menjadi method halaman yang menerima nomor halaman dan ukuran halaman:

import java.util.List;

public class HalamanProduk {

    record Produk(String nama, String kategori, int harga) {}

    static List<String> halaman(List<Produk> sumber, int nomor, int ukuran) {
        return sumber.stream()
                .skip((nomor - 1) * ukuran)
                .limit(ukuran)
                .map(Produk::nama)
                .toList();
    }

    public static void main(String[] args) {
        List<Produk> produk = List.of(
                new Produk("Kopi Arabika", "minuman", 85000),
                new Produk("Teh Hijau", "minuman", 45000),
                new Produk("Roti Gandum", "makanan", 30000),
                new Produk("Madu Hutan", "makanan", 120000),
                new Produk("Air Mineral", "minuman", 8000),
                new Produk("Gula Pasir", "bahan", 15000),
                new Produk("Beras Premium", "bahan", 68000)
        );

        System.out.println("Halaman 1: " + halaman(produk, 1, 3));
        System.out.println("Halaman 2: " + halaman(produk, 2, 3));
        System.out.println("Halaman 3: " + halaman(produk, 3, 3));
        System.out.println("Halaman 4: " + halaman(produk, 4, 3));
    }
}

Output yang Dihasilkan:

text
Halaman 1: [Kopi Arabika, Teh Hijau, Roti Gandum]
Halaman 2: [Madu Hutan, Air Mineral, Gula Pasir]
Halaman 3: [Beras Premium]
Halaman 4: []

Mari kita bedah pola pagination tersebut:

  1. Baris 7-13 (method halaman): seluruh logika paging hidup dalam empat operasi — skip melompati (nomor - 1) * ukuran elemen, limit menahan ukuran elemen, map mengambil nama, toList() menampung hasil.
  2. Baris 9 (skip(0) untuk halaman 1): (1 - 1) * 3 = 0, dan skip(0) tidak membuang apa pun — aman untuk halaman pertama tanpa cabang if.
  3. Baris 26-29: tujuh produk terbagi menjadi dua halaman penuh — halaman 1 berisi indeks 0-2, halaman 2 indeks 3-5 — lalu halaman 3 hanya berisi satu sisa elemen. Halaman 4 mencetak [] karena skip(9) melebihi jumlah elemen: stream tidak melempar galat, hanya menghasilkan stream kosong.
  4. Perilaku limit pada stream pendek: limit(n) tidak pernah menambah elemen — jika tersisa kurang dari n, seluruh sisa itu saja yang diloloskan, seperti Beras Premium pada halaman 3.

Kedua operasi ini juga berperan sebagai pengaman stream tak berujung: pola Stream.iterate(...).limit(n) yang kita pakai pada Chapter Creating Streams bekerja justru karena limit memotong aliran sebelum sumber kehabisan alasan berhenti.

takeWhile dan dropWhile Memotong Berdasar Kondisi #

takeWhile(predikat) mengambil elemen dari awal selama kondisinya terpenuhi, lalu memotong aliran di elemen pertama yang gagal — elemen setelahnya tidak diperiksa lagi walaupun sebenarnya lolos kondisi. dropWhile(predikat) adalah kebalikannya: membuang prefiks yang lolos kondisi, lalu meloloskan seluruh sisanya tanpa peduli kondisi. Perbedaan dengan filter tampak paling jelas lewat perbandingan berdampingan:

import java.util.List;

public class AmbilSampaiBatas {

    record Produk(String nama, String kategori, int harga) {}

    public static void main(String[] args) {
        List<Produk> produk = List.of(
                new Produk("Kopi Arabika", "minuman", 85000),
                new Produk("Teh Hijau", "minuman", 45000),
                new Produk("Madu Hutan", "makanan", 120000),
                new Produk("Roti Gandum", "makanan", 30000)
        );

        // filter memeriksa SETIAP elemen
        System.out.println("filter    : " + produk.stream()
                .filter(p -> p.harga() >= 50000)
                .map(Produk::nama)
                .toList());

        // takeWhile berhenti di pelanggaran PERTAMA
        System.out.println("takeWhile: " + produk.stream()
                .takeWhile(p -> p.harga() >= 50000)
                .map(Produk::nama)
                .toList());

        // dropWhile membuang prefiks yang lolos, lalu meloloskan sisanya
        System.out.println("dropWhile: " + produk.stream()
                .dropWhile(p -> p.harga() >= 50000)
                .map(Produk::nama)
                .toList());
    }
}

Output yang Dihasilkan:

text
filter    : [Kopi Arabika, Madu Hutan]
takeWhile: [Kopi Arabika]
dropWhile: [Teh Hijau, Madu Hutan, Roti Gandum]

Keluaran tersebut adalah bukti nyata perbedaan ketiganya. Mari kita baca baris demi baris:

  1. Baris 16-19 (filter): filter memeriksa keempat produk. Kopi Arabika (85000) dan Madu Hutan (120000) lolos; Teh Hijau dan Roti Gandum gagal. Urutan hasil mengikuti urutan sumber: [Kopi Arabika, Madu Hutan].
  2. Baris 22-25 (takeWhile): aliran mulai dibaca dari Kopi Arabika yang lolos, lalu berhenti total di Teh Hijau (45000) — pelanggaran pertama. Madu Hutan tidak pernah diperiksa padahal harganya 120000 dan akan lolos jika diuji; buktinya, hasil hanya [Kopi Arabika].
  3. Baris 28-31 (dropWhile): kebalikannya — Kopi Arabika dibuang sebagai prefiks yang lolos, aliran mulai mengalir dari Teh Hijau, dan seluruh sisa (Teh Hijau, Madu Hutan, Roti Gandum) diloloskan seutuhnya, termasuk Madu Hutan yang sebenarnya memenuhi kondisi.
  4. Sumber data pada contoh ini sengaja tidak terurut menurut harga — justru itu yang membuat perbedaan perilakunya terlihat. Ketika data terurut sesuai predikat, hasil filter dan takeWhile akan sama, tetapi takeWhile jauh lebih hemat karena berhenti di pelanggaran pertama.

Empat operasi pemotong ini melengkapi kotak peralatan penataan aliran. Setelah elemen tersaring, terpetakan, dan terpotong, tugas berikutnya adalah meringkasnya menjadi satu nilai — total omzet, stok gabungan, atau produk termahal. Chapter reduce membahas operasi agregasi paling dasar di balik semua perhitungan tersebut.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

AmbilSampaiBatas.java

Unduh berkas
java
import java.util.List;

public class AmbilSampaiBatas {

    record Produk(String nama, String kategori, int harga) {}

    public static void main(String[] args) {
        List<Produk> produk = List.of(
                new Produk("Kopi Arabika", "minuman", 85000),
                new Produk("Teh Hijau", "minuman", 45000),
                new Produk("Madu Hutan", "makanan", 120000),
                new Produk("Roti Gandum", "makanan", 30000)
        );

        // filter memeriksa SETIAP elemen
        System.out.println("filter    : " + produk.stream()
                .filter(p -> p.harga() >= 50000)
                .map(Produk::nama)
                .toList());

        // takeWhile berhenti di pelanggaran PERTAMA
        System.out.println("takeWhile: " + produk.stream()
                .takeWhile(p -> p.harga() >= 50000)
                .map(Produk::nama)
                .toList());

        // dropWhile membuang prefiks yang lolos, lalu meloloskan sisanya
        System.out.println("dropWhile: " + produk.stream()
                .dropWhile(p -> p.harga() >= 50000)
                .map(Produk::nama)
                .toList());
    }
}

HalamanProduk.java

Unduh berkas
java
import java.util.List;

public class HalamanProduk {

    record Produk(String nama, String kategori, int harga) {}

    static List<String> halaman(List<Produk> sumber, int nomor, int ukuran) {
        return sumber.stream()
                .skip((nomor - 1) * ukuran)
                .limit(ukuran)
                .map(Produk::nama)
                .toList();
    }

    public static void main(String[] args) {
        List<Produk> produk = List.of(
                new Produk("Kopi Arabika", "minuman", 85000),
                new Produk("Teh Hijau", "minuman", 45000),
                new Produk("Roti Gandum", "makanan", 30000),
                new Produk("Madu Hutan", "makanan", 120000),
                new Produk("Air Mineral", "minuman", 8000),
                new Produk("Gula Pasir", "bahan", 15000),
                new Produk("Beras Premium", "bahan", 68000)
        );

        System.out.println("Halaman 1: " + halaman(produk, 1, 3));
        System.out.println("Halaman 2: " + halaman(produk, 2, 3));
        System.out.println("Halaman 3: " + halaman(produk, 3, 3));
        System.out.println("Halaman 4: " + halaman(produk, 4, 3));
    }
}