Dua interface penutup quartet dasar java.util.function bekerja di ujung-ujung arus data. Consumer<T> berada di ujung hilir: menerima satu nilai lewat accept dan tidak mengembalikan apa pun — seluruh gunanya adalah menjalankan aksi terhadap nilai itu, mencetak, mencatat log, atau mengirimnya ke tempat lain. Supplier<T> berada di ujung hulu: tidak menerima masukan sama sekali, tetapi memproduksi satu nilai lewat get — pola dasar factory dan evaluasi lazy. Keduanya tidak menghitung apa pun; mereka menggerakkan data keluar-masuk program kita.

Consumer dan Aksi Tanpa Keluaran #

Method accept(T) mengingatkan kita bahwa tidak semua pekerjaan menghasilkan nilai. Logging, notifikasi, penulisan ke berkas, dan pencetakan ke terminal semuanya bekerja untuk efek sampingnya, bukan untuk nilai kembaliannya — dan Consumer adalah bahasa umum untuk pekerjaan semacam itu. Method andThen juga tersedia: dua Consumer dirangkai sehingga nilai yang sama diproses dua aksi berurutan.

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

public class AlurKonsumen {
    static void catat(String pesan) {
        System.out.println("[LOG] " + pesan);
    }

    public static void main(String[] args) {
        // Consumer dasar: satu nilai masuk, tidak ada nilai keluaran
        Consumer<String> pencatat = pesan -> catat(pesan);
        pencatat.accept("Aplikasi dimulai");

        // andThen: dua aksi dijalankan berurutan pada nilai yang sama
        Consumer<String> verifikasi = pesan ->
                System.out.println("[CEK] " + pesan.length() + " karakter");
        Consumer<String> alurLengkap = pencatat.andThen(verifikasi);
        alurLengkap.accept("Sinkronisasi data");

        // forEach pada List menerima Consumer per elemen
        List<String> tugas = List.of("backup", "kirim email", "laporan");
        tugas.forEach(item -> System.out.println("- " + item));

        // BiConsumer pada Map.forEach: kunci dan nilai sekaligus
        Map<String, Integer> stok = new LinkedHashMap<>();
        stok.put("kopi", 12);
        stok.put("teh", 30);
        stok.put("susu", 7);
        stok.forEach((barang, jumlah) ->
                System.out.println(barang + " : " + jumlah + " unit"));
    }
}

Output yang Dihasilkan:

text
[LOG] Aplikasi dimulai
[LOG] Sinkronisasi data
[CEK] 17 karakter
- backup
- kirim email
- laporan
kopi : 12 unit
teh : 30 unit
susu : 7 unit

Mari kita bedah program tersebut:

  1. Baris 14-15 (pencatat.accept(...)): satu String masuk, method catat dijalankan, tidak ada nilai keluaran. Lambda pesan -> catat(pesan) membungkus method biasa menjadi nilai yang bisa dikirim ke API mana pun yang meminta Consumer<String>.
  2. Baris 18-21 (alurLengkap = pencatat.andThen(verifikasi)): andThen menggabungkan dua aksi — catat dulu, periksa panjangnya kemudian. Satu panggilan accept("Sinkronisasi data") memicu kedua baris keluaran: [LOG] ... lalu [CEK] 17 karakter.
  3. Baris 24-25 (tugas.forEach(...)): forEach pada List menerima Consumer<T> dan menjalankan sekali per elemen. Lambda item -> System.out.println("- " + item) adalah Consumer<String> yang bekerja tanpa kita mendeklarasikan variabel bertipe itu.
  4. Baris 27-33 (stok.forEach(...)): di sinilah BiConsumer<T, U> bersinar. Map.forEach mengirimkan pasangan kunci-nilai (barang, jumlah) pada setiap iterasi — dua nilai sekaligus, sesuatu yang Consumer<T> tunggal tidak sanggup pegang. LinkedHashMap dipakai agar urutan keluaran mengikuti urutan penyisipan.

Supplier sebagai Factory dan Penunda Eksekusi #

Supplier<T> membalik arah data: tidak ada masukan, hanya keluaran get(). Dua pemakaian utamanya adalah factory (resep pembuatan objek yang bisa dipanggil berulang) dan lazy evaluation (pekerjaan berat ditunda sampai benar-benar diperlukan). Program berikut membuktikan kedua sifat itu dengan penghitung panggilan:

import java.util.function.Supplier;

public class PemasokNilai {
    static int jumlahPanggilan = 0;

    static String susunLaporan() {
        jumlahPanggilan++;
        return "Laporan penjualan Q4";
    }

    public static void main(String[] args) {
        Supplier<String> pabrikLaporan = () -> susunLaporan();

        // Deklarasi ini TIDAK menjalankan susunLaporan()
        Supplier<String> masihRencana = pabrikLaporan;
        System.out.println("Sebelum get(): panggilan = " + jumlahPanggilan);

        // Barulah di sini laporan benar-benar disusun
        String laporan = masihRencana.get();
        System.out.println("Setelah get() : panggilan = " + jumlahPanggilan);
        System.out.println(laporan);

        // Supplier sebagai factory: setiap get() membuat objek baru
        Supplier<StringBuilder> pabrikTeks = StringBuilder::new;
        StringBuilder a = pabrikTeks.get();
        StringBuilder b = pabrikTeks.get();
        a.append("terpisah");
        System.out.println("a = " + a + ", b kosong? " + (b.length() == 0));
    }
}

Output yang Dihasilkan:

text
Sebelum get(): panggilan = 0
Setelah get() : panggilan = 1
Laporan penjualan Q4
a = terpisah, b kosong? true

Bedah buktinya baris demi baris:

  1. Baris 22-24 (masihRencana = pabrikLaporan): menyalin Supplier ke variabel lain tidak menjalankan susunLaporan() — penghitung masih 0 pada baris Sebelum get(). Lambda menyimpan resep, bukan hasil.
  2. Baris 27 (masihRencana.get()): panggilan get() barulah mengeksekusi resep itu; penghitung naik menjadi 1 dan laporan jadi. Inilah dasar evaluasi lazy di banyak API — misalnya Optional.orElseGet(Supplier) yang hanya memanggil supplier ketika Optional kosong.
  3. Baris 33-37 (pabrikTeks = StringBuilder::new): dua panggilan get() menghasilkan dua objek StringBuilder yang berbeda. Setelah a diisi "terpisah", panjang b tetap 0 — setiap get() menjalankan new StringBuilder() segar, sesuai peran factory.
  4. Baris 5 (static int jumlahPanggilan): penghitung berupa variabel static sengaja dipakai agar bukti kapan eksekusi terjadi terlihat di keluaran. Variabel semacam ini adalah side effect yang disengaja untuk keperluan pembelajaran — pada kode produksi, penghitungan seperti ini lebih aman dihindari atau diisolasi.

Melengkapi Quartet Dasar #

Dengan Predicate (uji), Function (ubah), Consumer (pakai), dan Supplier (produksi), kita kini memegang keempat kata kerja dasar pemrograman fungsional di Java. Empat kata kerja itu cukup untuk menjelaskan hampir seluruh isi java.util.function — sisanya adalah variasi jumlah parameter, variasi tipe primitif, dan variasi arah masukan-keluaran. Chapter berikutnya mengambil langkah berikutnya yang paling fungsional: merangkai Function-Function menjadi pipeline — dan membedakan andThen dari compose yang urutannya terbalik.

Referensi #

Berkas Sumber

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

AlurKonsumen.java

Unduh berkas
java
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

public class AlurKonsumen {
    static void catat(String pesan) {
        System.out.println("[LOG] " + pesan);
    }

    public static void main(String[] args) {
        // Consumer dasar: satu nilai masuk, tidak ada nilai keluaran
        Consumer<String> pencatat = pesan -> catat(pesan);
        pencatat.accept("Aplikasi dimulai");

        // andThen: dua aksi dijalankan berurutan pada nilai yang sama
        Consumer<String> verifikasi = pesan ->
                System.out.println("[CEK] " + pesan.length() + " karakter");
        Consumer<String> alurLengkap = pencatat.andThen(verifikasi);
        alurLengkap.accept("Sinkronisasi data");

        // forEach pada List menerima Consumer per elemen
        List<String> tugas = List.of("backup", "kirim email", "laporan");
        tugas.forEach(item -> System.out.println("- " + item));

        // BiConsumer pada Map.forEach: kunci dan nilai sekaligus
        Map<String, Integer> stok = new LinkedHashMap<>();
        stok.put("kopi", 12);
        stok.put("teh", 30);
        stok.put("susu", 7);
        stok.forEach((barang, jumlah) ->
                System.out.println(barang + " : " + jumlah + " unit"));
    }
}

PemasokNilai.java

Unduh berkas
java
import java.util.function.Supplier;

public class PemasokNilai {
    static int jumlahPanggilan = 0;

    static String susunLaporan() {
        jumlahPanggilan++;
        return "Laporan penjualan Q4";
    }

    public static void main(String[] args) {
        Supplier<String> pabrikLaporan = () -> susunLaporan();

        // Deklarasi ini TIDAK menjalankan susunLaporan()
        Supplier<String> masihRencana = pabrikLaporan;
        System.out.println("Sebelum get(): panggilan = " + jumlahPanggilan);

        // Barulah di sini laporan benar-benar disusun
        String laporan = masihRencana.get();
        System.out.println("Setelah get() : panggilan = " + jumlahPanggilan);
        System.out.println(laporan);

        // Supplier sebagai factory: setiap get() membuat objek baru
        Supplier<StringBuilder> pabrikTeks = StringBuilder::new;
        StringBuilder a = pabrikTeks.get();
        StringBuilder b = pabrikTeks.get();
        a.append("terpisah");
        System.out.println("a = " + a + ", b kosong? " + (b.length() == 0));
    }
}