Kita bisa membuat functional interface sendiri, tetapi membiasakannya berbahaya secara sosial: setiap proyek akan mengembangkan Validasi, Checker, dan Pemeriksa dengan bentuk yang sama, dan pembaca kode harus menghafal nama baru terus-menerus. Java menyelesaikannya dengan paket java.util.function — katalog interface bawaan yang menutup hampir semua kebutuhan. Aturan dari komunitas Java: jangan membuat interface sendiri bila versi bawaan sudah cocok. Pada Chapter ini kita memetakan katalog itu dan memahami mengapa ia juga berisi ratusan varian primitif seperti IntPredicate.

Peta Keluarga java.util.function #

Seluruh isi paket itu pada dasarnya adalah kombinasi dari empat bentuk dasar. Tabel berikut memetakan yang paling sering dipakai:

InterfaceMethod abstrakMaknaContoh pemakaian
Predicate<T>boolean test(T)menguji kondisifilter, validasi
Function<T, R>R apply(T)mengubah nilaitransformasi data
Consumer<T>void accept(T)mengonsumsi nilailogging, aksi per elemen
Supplier<T>T get()memproduksi nilaifactory, nilai lazy
UnaryOperator<T>T apply(T)mengubah ke tipe samaString kapital
BinaryOperator<T>T apply(T, T)menggabungkan dua nilaireduce, penjumlahan
BiPredicate<T, U>boolean test(T, U)menguji dua nilaipencocokan pasangan
BiFunction<T, U, R>R apply(T, U)mengubah dua nilaipenggabungan data
BiConsumer<T, U>void accept(T, U)mengonsumsi dua nilaiiterasi pasangan kunci-nilai

Tiga baris pertama adalah akar dari semuanya: UnaryOperator hanyalah Function<T, T>, BinaryOperator adalah BiFunction<T, T, T>, dan semua varian Bi... memperpanjang daftar parameter menjadi dua. Program berikut mengeksekusi sembilan interface tabel di atas dalam satu berkas:

import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;

public class KatalogBawaan {
    public static void main(String[] args) {
        // Empat interface dasar
        Predicate<String> kosong = String::isEmpty;            // T -> boolean
        Function<String, Integer> panjang = String::length;    // T -> R
        Consumer<String> cetak = System.out::println;          // T -> void
        Supplier<String> salam = () -> "Halo, pelanggan!";     // () -> T

        // Interface turunan dan versi dua-argumen
        UnaryOperator<String> kapital = String::toUpperCase;   // T -> T
        BinaryOperator<Integer> total = (a, b) -> a + b;       // (T, T) -> T
        BiPredicate<String, Integer> cocok = (teks, n) -> teks.length() == n;
        BiFunction<String, String, String> gabung = (a, b) -> a + " & " + b;
        BiConsumer<String, Integer> label = (nama, harga) ->
                System.out.println(nama + " Rp" + harga);

        System.out.println("teks kosong? " + kosong.test(""));
        System.out.println("panjang \"lambda\" = " + panjang.apply("lambda"));
        cetak.accept(salam.get());
        System.out.println(kapital.apply("deklaratif"));
        System.out.println("total = " + total.apply(40, 2));
        System.out.println("panjang cocok? " + cocok.test("budi", 4));
        System.out.println(gabung.apply("imperatif", "deklaratif"));
        label.accept("Kopi", 25000);
    }
}

Output yang Dihasilkan:

text
teks kosong? true
panjang "lambda" = 6
Halo, pelanggan!
DEKLARATIF
total = 42
panjang cocok? true
imperatif & deklaratif
Kopi Rp25000

Mari kita bedah titik-titik kuncinya:

  1. Baris 12-15 (empat dasar): Predicate menerima String dan mengembalikan boolean — di sini diisi method reference String::isEmpty karena bentuknya persis teks -> teks.isEmpty(). Function, Consumer, dan Supplier masing-masing menegaskan pola masuk-diubah-keluar, masuk-tanpa-keluar, dan keluar-tanpa-masukan.
  2. Baris 18 (UnaryOperator<String> kapital): tipe masuk dan keluar sama (String → String), sehingga satu parameter tipe cukup. String::toUpperCase cocok tanpa menyebut tipe dua kali.
  3. Baris 19 (BinaryOperator<Integer> total): dua nilai bertipe sama digabung menjadi satu bertipe sama juga — pola khas operasi reduce seperti penjumlahan.
  4. Baris 24-25 (BiConsumer<String, Integer> label): dua nilai dikonsumsi tanpa hasil; tubuh lambda-nya mencetak label harga. Kombinasi (String, Integer) juga bentuk khas pasangan nama-nilai pada Map.forEach.

Varian Primitif dan Masalah Autoboxing #

Mengapa katalog itu berisi IntPredicate, IntFunction, LongSupplier, DoubleUnaryOperator, dan puluhan nama serupa? Jawabannya: autoboxing. Predicate<Integer> hanya bisa memegang objek Integer; setiap test(8) pada bilangan int memaksa JVM membungkus 8 menjadi objek Integer di heap, lalu membukanya kembali. Satu bungkus tidak terasa — satu juta bungkus di dalam loop panas menjadi tekanan memori dan garbage collection yang nyata. Varian primitif bekerja langsung pada int, long, atau double tanpa perungkus apa pun.

import java.util.function.IntFunction;
import java.util.function.IntPredicate;
import java.util.function.IntSupplier;
import java.util.function.IntUnaryOperator;
import java.util.function.Predicate;
import java.util.function.ToIntFunction;

public class VarianPrimitif {
    public static void main(String[] args) {
        // Versi generic: setiap test() mem-box int menjadi Integer
        Predicate<Integer> genapBoks = n -> n % 2 == 0;

        // Versi primitif: bekerja langsung pada int, tanpa boxing
        IntPredicate genapLangsung = n -> n % 2 == 0;

        System.out.println("Predicate<Integer> : " + genapBoks.test(8));
        System.out.println("IntPredicate       : " + genapLangsung.test(8));

        // Keluarga primitif lengkap: masuk, keluar, atau keduanya
        IntSupplier stokAwal = () -> 120;
        ToIntFunction<String> hitungKarakter = String::length;
        IntUnaryOperator potongHarga = harga -> harga - 10000;
        IntFunction<String> rupiah = nilai -> "Rp" + nilai;

        System.out.println("stok awal   : " + stokAwal.getAsInt());
        System.out.println("karakter    : " + hitungKarakter.applyAsInt("pemrograman"));
        System.out.println("harga akhir : " + rupiah.apply(potongHarga.applyAsInt(25000)));
    }
}

Output yang Dihasilkan:

text
Predicate<Integer> : true
IntPredicate       : true
stok awal   : 120
karakter    : 11
harga akhir : Rp15000

Bedah berkas tersebut:

  1. Baris 10 dan 13: dua baris dengan makna sama — menguji kegenapan. Predicate<Integer> menerima Integer (jembatan objek), sedangkan IntPredicate menerima int polos. Keluaran keduanya identik; perbedaannya hanya pada lalu lintas objek di belakang layar.
  2. Baris 22 (IntSupplier stokAwal): method abstraknya getAsInt(), bukan get() — penanda bahwa hasilnya int primitif langsung dari sumbernya.
  3. Baris 23 (ToIntFunction<String> hitungKarakter): varian keluar-primitif: masukan tetap String, keluaran int lewat applyAsInt. String::length mengembalikan int sehingga pas tanpa boxing.
  4. Baris 24-25 (IntUnaryOperator dan IntFunction<String>): potongHarga mengubah int menjadi int; rupiah mengubah int menjadi String. Pada baris 30 keduanya dirangkai: potongHarga.applyAsInt(25000) menghasilkan 15000, lalu rupiah.apply(15000) menghasilkan "Rp15000".

Kapan Tetap Membuat Interface Sendiri #

Ada dua alasan yang sah membuat functional interface kustom. Pertama, nama domain yang kuat membuat API bicara: PemberiDiskon pada Chapter Functional Interfaces lebih bermakna di konteks toko daripada Function<Double, Double>. Kedua, interface kustom boleh membawa method default pendukung yang tidak mungkin ditempel pada interface bawaan. Di luar dua alasan itu — terutama untuk parameter method internal — java.util.function hampir selalu punya kandidat yang tepat.

Katalog ini juga menjadi bekal untuk tiga Chapter berikutnya, yang masing-masing menyelami satu interface dasar secara utuh: Predicate untuk logika kondisi, Function untuk transformasi, dan Consumer-Supplier untuk arus data tanpa hasil dan tanpa masukan.

Referensi #

Berkas Sumber

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

KatalogBawaan.java

Unduh berkas
java
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;

public class KatalogBawaan {
    public static void main(String[] args) {
        // Empat interface dasar
        Predicate<String> kosong = String::isEmpty;            // T -> boolean
        Function<String, Integer> panjang = String::length;    // T -> R
        Consumer<String> cetak = System.out::println;          // T -> void
        Supplier<String> salam = () -> "Halo, pelanggan!";     // () -> T

        // Interface turunan dan versi dua-argumen
        UnaryOperator<String> kapital = String::toUpperCase;   // T -> T
        BinaryOperator<Integer> total = (a, b) -> a + b;       // (T, T) -> T
        BiPredicate<String, Integer> cocok = (teks, n) -> teks.length() == n;
        BiFunction<String, String, String> gabung = (a, b) -> a + " & " + b;
        BiConsumer<String, Integer> label = (nama, harga) ->
                System.out.println(nama + " Rp" + harga);

        System.out.println("teks kosong? " + kosong.test(""));
        System.out.println("panjang \"lambda\" = " + panjang.apply("lambda"));
        cetak.accept(salam.get());
        System.out.println(kapital.apply("deklaratif"));
        System.out.println("total = " + total.apply(40, 2));
        System.out.println("panjang cocok? " + cocok.test("budi", 4));
        System.out.println(gabung.apply("imperatif", "deklaratif"));
        label.accept("Kopi", 25000);
    }
}

VarianPrimitif.java

Unduh berkas
java
import java.util.function.IntFunction;
import java.util.function.IntPredicate;
import java.util.function.IntSupplier;
import java.util.function.IntUnaryOperator;
import java.util.function.Predicate;
import java.util.function.ToIntFunction;

public class VarianPrimitif {
    public static void main(String[] args) {
        // Versi generic: setiap test() mem-box int menjadi Integer
        Predicate<Integer> genapBoks = n -> n % 2 == 0;

        // Versi primitif: bekerja langsung pada int, tanpa boxing
        IntPredicate genapLangsung = n -> n % 2 == 0;

        System.out.println("Predicate<Integer> : " + genapBoks.test(8));
        System.out.println("IntPredicate       : " + genapLangsung.test(8));

        // Keluarga primitif lengkap: masuk, keluar, atau keduanya
        IntSupplier stokAwal = () -> 120;
        ToIntFunction<String> hitungKarakter = String::length;
        IntUnaryOperator potongHarga = harga -> harga - 10000;
        IntFunction<String> rupiah = nilai -> "Rp" + nilai;

        System.out.println("stok awal   : " + stokAwal.getAsInt());
        System.out.println("karakter    : " + hitungKarakter.applyAsInt("pemrograman"));
        System.out.println("harga akhir : " + rupiah.apply(potongHarga.applyAsInt(25000)));
    }
}