Setiap keputusan dalam program — pelamar lolos atau tidak, pesanan valid atau ditolak — pada dasarnya adalah fungsi dari nilai menuju boolean. Predicate<T> mengangkat pola itu menjadi nilai kelas satu: kondisi yang bisa disimpan di variabel, dikirim ke method, dan paling penting — digabungkan dengan operasi and, or, serta negate tanpa menulis satu pernyataan if baru. Pada Chapter ini kita membedah Predicate sebagai interface pertama dari trio java.util.function yang akan kita dalami, lalu menerapkannya untuk menyaring daftar dan memvalidasi pesanan.

Menguji Nilai dengan test #

Predicate<T> punya satu method abstrak: boolean test(T t) — berikan satu nilai, terima jawaban true atau false. Kekuatan sesungguhnya muncul dari tiga method default kombinatornya: and (kedua kondisi harus benar), or (salah satu cukup), dan negate (membalik hasil). Ketiganya mengembalikan Predicate baru, sehingga kondisi majemuk dibangun dengan menyusun potongan-potongan kecil — persis seperti operator logika &&, ||, dan !, tetapi dalam level nilai, bukan level statement.

import java.util.ArrayList;
import java.util.List;
import java.util.function.BiPredicate;
import java.util.function.Predicate;

public class SaringPelamar {

    // Filter manual: syarat datang sebagai Predicate, loop tetap eksplisit
    static List<String> saring(List<String> data, Predicate<String> syarat) {
        List<String> hasil = new ArrayList<>();
        for (String item : data) {
            if (syarat.test(item)) {
                hasil.add(item);
            }
        }
        return hasil;
    }

    public static void main(String[] args) {
        List<String> pelamar = List.of("Budi", "Siti", "Dewi", "Ani", "Rahmat");

        Predicate<String> panjangMinimal = nama -> nama.length() >= 4;
        Predicate<String> diawaliHurufD = nama -> nama.startsWith("D");

        System.out.println("Panjang >= 4    : " + saring(pelamar, panjangMinimal));
        System.out.println("Diawali huruf D : " + saring(pelamar, diawaliHurufD));
        System.out.println("and             : " + saring(pelamar, panjangMinimal.and(diawaliHurufD)));
        System.out.println("or              : " + saring(pelamar, panjangMinimal.or(diawaliHurufD)));
        System.out.println("negate          : " + saring(pelamar, diawaliHurufD.negate()));

        // BiPredicate: dua nilai masuk, keputusan keluar
        BiPredicate<String, Integer> panjangTepat = (nama, n) -> nama.length() == n;
        System.out.println("Dewi 4 huruf?   : " + panjangTepat.test("Dewi", 4));
    }
}

Output yang Dihasilkan:

text
Panjang >= 4    : [Budi, Siti, Dewi, Rahmat]
Diawali huruf D : [Dewi]
and             : [Dewi]
or              : [Budi, Siti, Dewi, Rahmat]
negate          : [Budi, Siti, Ani, Rahmat]
Dewi 4 huruf?   : true

Mari kita bedah program tersebut baris demi baris:

  1. Baris 7-14 (method saring): ini adalah filter buatan sendiri — loop for eksplisit yang memanggil syarat.test(item) untuk setiap elemen. Perhatikan bahwa method ini tidak tahu sedang menyaring apa; seluruh kebijakan hidup di dalam Predicate yang dikirim pemanggil.
  2. Baris 20-21: dua kondisi dasar dibuat terpisah: panjangMinimal (nama minimal empat huruf) dan diawaliHurufD. Keduanya adalah Predicate<String> biasa — nilai boolean yang bisa dibawa ke mana saja.
  3. Baris 23-24: dua pemanggilan saring pertama memakai kondisi dasar berdiri sendiri. Hasilnya dua laporan berbeda dari satu method penyaring.
  4. Baris 25 (panjangMinimal.and(diawaliHurufD)): and menggabungkan dua kondisi menjadi Predicate baru yang hanya meloloskan item yang lolos keduanya — dari kelima pelamar hanya "Dewi" yang panjangnya minimal empat dan diawali huruf D.
  5. Baris 26 (or) dan Baris 27 (negate): or meloloskan gabungan kedua himpunan, sedangkan diawaliHurufD.negate() membalik kondisi menjadi “bukan yang diawali huruf D”. Perhatikan bahwa negate bekerja tanpa menulis nama -> !nama.startsWith("D") — kondisi lama dipakai ulang apa adanya.
  6. Baris 30-31 (BiPredicate<String, Integer>): varian dua masukan. test("Dewi", 4) menjawab kecocokan pasangan nilai — berguna saat keputusan membutuhkan dua data sekaligus, misalnya membandingkan kunci dengan tabel aturan.

Predicate di Dunia Validasi #

Pola kombinasi Predicate paling sering dipakai untuk aturan validasi berlapis. Program berikut memeriksa daftar pesanan terhadap dua aturan bisnis; aturan disimpan dalam List<Predicate<Pesanan>> sehingga menambah aturan baru tidak menyentuh method pemeriksanya:

import java.util.List;
import java.util.function.Predicate;

public class ValidasiPesanan {

    record Pesanan(String id, int jumlah, double total) {}

    static void periksa(Pesanan p, List<Predicate<Pesanan>> aturan) {
        for (Predicate<Pesanan> aturanSatu : aturan) {
            if (!aturanSatu.test(p)) {
                System.out.println(p.id() + " DITOLAK");
                return;
            }
        }
        System.out.println(p.id() + " LOLOS");
    }

    public static void main(String[] args) {
        Predicate<Pesanan> jumlahWajar = p -> p.jumlah() > 0 && p.jumlah() <= 100;
        Predicate<Pesanan> totalWajar = p -> p.total() >= 0;

        List<Pesanan> pesanan = List.of(
                new Pesanan("A-1", 3, 75000),
                new Pesanan("A-2", 0, 5000),
                new Pesanan("A-3", 250, 120000));

        for (Pesanan p : pesanan) {
            periksa(p, List.of(jumlahWajar, totalWajar));
        }
    }
}

Output yang Dihasilkan:

text
A-1 LOLOS
A-2 DITOLAK
A-3 DITOLAK

Tiga hal patut dicermati dari berkas tersebut:

  1. Baris 6 (record Pesanan(...)): record menyediakan aksesor ringkas (p.jumlah(), p.total()) — pas untuk data yang hanya diperiksa, tidak diubah.
  2. Baris 8-18 (method periksa): loop menjalankan setiap aturan lewat test; pesanan pertama yang gagal langsung menghasilkan DITOLAK. Method ini sepenuhnya generik: daftar aturan datang dari luar, jadi periksa tidak perlu berubah ketika kebijakan bisnis bertambah.
  3. Baris 21-22: dua aturan dideklarasikan sebagai variabel bernama. Nama jumlahWajar dan totalWajar membuat daftar aturan terbaca seperti dokumen kebijakan — bandingkan dengan satu if raksasa berisi enam perbandingan yang harus dibedah pembaca baris demi baris.

Predicate untuk Dua Nilai #

BiPredicate<T, U> meluaskan gagasan pengujian ke dua nilai dengan tipe berbeda — misalnya mencocokkan String dengan Integer, atau entitas dengan konteks. Kombinator and/or/negate juga tersedia pada BiPredicate, jadi pasangan kondisi dua-nilai tetap bisa disusun deklaratif. Untuk tiga nilai atau lebih, konvensi Java mengarahkan kita membungkus nilai-nilai itu dalam satu record lalu memakai Predicate<Record> tunggal — pendekatan yang lebih mudah dibaca daripada interface berparameter panjang.

Dari kondisi boolean kita naik satu tingkat: bagaimana jika hasilnya bukan sekadar true/false, melainkan nilai baru? Chapter berikutnya membahas Function dan BiFunction — mesin transformasi yang menjadi bahan bakar utama pipeline fungsional.

Referensi #

Berkas Sumber

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

SaringPelamar.java

Unduh berkas
java
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiPredicate;
import java.util.function.Predicate;

public class SaringPelamar {

    // Filter manual: syarat datang sebagai Predicate, loop tetap eksplisit
    static List<String> saring(List<String> data, Predicate<String> syarat) {
        List<String> hasil = new ArrayList<>();
        for (String item : data) {
            if (syarat.test(item)) {
                hasil.add(item);
            }
        }
        return hasil;
    }

    public static void main(String[] args) {
        List<String> pelamar = List.of("Budi", "Siti", "Dewi", "Ani", "Rahmat");

        Predicate<String> panjangMinimal = nama -> nama.length() >= 4;
        Predicate<String> diawaliHurufD = nama -> nama.startsWith("D");

        System.out.println("Panjang >= 4    : " + saring(pelamar, panjangMinimal));
        System.out.println("Diawali huruf D : " + saring(pelamar, diawaliHurufD));
        System.out.println("and             : " + saring(pelamar, panjangMinimal.and(diawaliHurufD)));
        System.out.println("or              : " + saring(pelamar, panjangMinimal.or(diawaliHurufD)));
        System.out.println("negate          : " + saring(pelamar, diawaliHurufD.negate()));

        // BiPredicate: dua nilai masuk, keputusan keluar
        BiPredicate<String, Integer> panjangTepat = (nama, n) -> nama.length() == n;
        System.out.println("Dewi 4 huruf?   : " + panjangTepat.test("Dewi", 4));
    }
}

ValidasiPesanan.java

Unduh berkas
java
import java.util.List;
import java.util.function.Predicate;

public class ValidasiPesanan {

    record Pesanan(String id, int jumlah, double total) {}

    static void periksa(Pesanan p, List<Predicate<Pesanan>> aturan) {
        for (Predicate<Pesanan> aturanSatu : aturan) {
            if (!aturanSatu.test(p)) {
                System.out.println(p.id() + " DITOLAK");
                return;
            }
        }
        System.out.println(p.id() + " LOLOS");
    }

    public static void main(String[] args) {
        Predicate<Pesanan> jumlahWajar = p -> p.jumlah() > 0 && p.jumlah() <= 100;
        Predicate<Pesanan> totalWajar = p -> p.total() >= 0;

        List<Pesanan> pesanan = List.of(
                new Pesanan("A-1", 3, 75000),
                new Pesanan("A-2", 0, 5000),
                new Pesanan("A-3", 250, 120000));

        for (Pesanan p : pesanan) {
            periksa(p, List.of(jumlahWajar, totalWajar));
        }
    }
}