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:
| Interface | Method abstrak | Makna | Contoh pemakaian |
|---|---|---|---|
Predicate<T> | boolean test(T) | menguji kondisi | filter, validasi |
Function<T, R> | R apply(T) | mengubah nilai | transformasi data |
Consumer<T> | void accept(T) | mengonsumsi nilai | logging, aksi per elemen |
Supplier<T> | T get() | memproduksi nilai | factory, nilai lazy |
UnaryOperator<T> | T apply(T) | mengubah ke tipe sama | String kapital |
BinaryOperator<T> | T apply(T, T) | menggabungkan dua nilai | reduce, penjumlahan |
BiPredicate<T, U> | boolean test(T, U) | menguji dua nilai | pencocokan pasangan |
BiFunction<T, U, R> | R apply(T, U) | mengubah dua nilai | penggabungan data |
BiConsumer<T, U> | void accept(T, U) | mengonsumsi dua nilai | iterasi 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:
teks kosong? true
panjang "lambda" = 6
Halo, pelanggan!
DEKLARATIF
total = 42
panjang cocok? true
imperatif & deklaratif
Kopi Rp25000Mari kita bedah titik-titik kuncinya:
- Baris 12-15 (empat dasar):
PredicatemenerimaStringdan mengembalikanboolean— di sini diisi method referenceString::isEmptykarena bentuknya persisteks -> teks.isEmpty().Function,Consumer, danSuppliermasing-masing menegaskan pola masuk-diubah-keluar, masuk-tanpa-keluar, dan keluar-tanpa-masukan. - Baris 18 (
UnaryOperator<String> kapital): tipe masuk dan keluar sama (String→String), sehingga satu parameter tipe cukup.String::toUpperCasecocok tanpa menyebut tipe dua kali. - Baris 19 (
BinaryOperator<Integer> total): dua nilai bertipe sama digabung menjadi satu bertipe sama juga — pola khas operasi reduce seperti penjumlahan. - 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 padaMap.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:
Predicate<Integer> : true
IntPredicate : true
stok awal : 120
karakter : 11
harga akhir : Rp15000Bedah berkas tersebut:
- Baris 10 dan 13: dua baris dengan makna sama — menguji kegenapan.
Predicate<Integer>menerimaInteger(jembatan objek), sedangkanIntPredicatemenerimaintpolos. Keluaran keduanya identik; perbedaannya hanya pada lalu lintas objek di belakang layar. - Baris 22 (
IntSupplier stokAwal): method abstraknyagetAsInt(), bukanget()— penanda bahwa hasilnyaintprimitif langsung dari sumbernya. - Baris 23 (
ToIntFunction<String> hitungKarakter): varian keluar-primitif: masukan tetapString, keluaranintlewatapplyAsInt.String::lengthmengembalikanintsehingga pas tanpa boxing. - Baris 24-25 (
IntUnaryOperatordanIntFunction<String>):potongHargamengubahintmenjadiint;rupiahmengubahintmenjadiString. Pada baris 30 keduanya dirangkai:potongHarga.applyAsInt(25000)menghasilkan15000, lalurupiah.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 #
- Oracle: java.util.function (Package Summary)
- dev.java: Lambda Expressions
- Oracle: java.util.function.Predicate
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
KatalogBawaan.java
Unduh berkasimport 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 berkasimport 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)));
}
}