Menyusun program dari fungsi-fungsi kecil baru bergaya fungsional ketika fungsi-fungsi itu bisa dirangkai — keluaran satu transformasi mengalir menjadi masukan transformasi berikutnya tanpa variabel perantara dan tanpa loop penjalin manual. Java menyediakan dua penggabung untuk itu: andThen dan compose. Keduanya menghasilkan Function baru dari dua Function lama; bedanya hanya pada urutan eksekusi — dan urutan itulah yang paling sering tertukar oleh programmer. Pada Chapter ini kita membangun pipeline teks berlapis, lalu membuktikan urutan kedua penggabung dengan jejak println yang bisa kita lihat langsung.

Pipeline dari Tahap-Tahap Kecil #

Tugasnya sederhana: hitung jumlah kata pada teks apa pun, dengan syarat teks dirapikan lebih dulu. Alih-alih menulis satu method besar int hitungKata(String teks) yang berisi trim(), toLowerCase(), split(), dan penghitungan sekaligus, kita pecah menjadi empat Function kecil lalu sambungkan dengan andThen:

import java.util.function.Function;

public class PipelineTeks {
    public static void main(String[] args) {
        // Empat tahap kecil, masing-masing satu tanggung jawab
        Function<String, String> rapikan = String::trim;
        Function<String, String> kecilkan = String::toLowerCase;
        Function<String, String[]> pecah = teks -> teks.split(" ");
        Function<String[], Integer> hitung = kata -> kata.length;

        // Pipeline: keluaran setiap tahap mengalir ke tahap berikutnya
        Function<String, Integer> jumlahKata = rapikan
                .andThen(kecilkan)
                .andThen(pecah)
                .andThen(hitung);

        String masukan = "  Belajar Function Composition di Java  ";
        System.out.println("Jumlah kata : " + jumlahKata.apply(masukan));
        System.out.println("Tahap rapikan saja : [" + rapikan.apply(masukan) + "]");
    }
}

Output yang Dihasilkan:

text
Jumlah kata : 5
Tahap rapikan saja : [Belajar Function Composition di Java]

Mari kita bedah alur data pada program tersebut:

  1. Baris 6-9 (empat tahap): setiap tahap memegang satu tanggung jawab — rapikan membuang spasi tepi, kecilkan menyeragamkan kapital, pecah membelah menjadi array kata, hitung menghitung panjang array. Tipe-tipe parameternya membentuk rantai String → String → String → String[] → Integer.
  2. Baris 12-16 (rapikan.andThen(kecilkan).andThen(pecah).andThen(hitung)): andThen menempel tahap berikutnya di sisi kanan; hasil penempelan adalah Function baru, sehingga penempelan berikutnya bisa dilakukan lagi — pola berantai persis seperti memanggil method berurutan. Variabel jumlahKata bertipe Function<String, Integer>: dari teks mentah langsung ke angka.
  3. Baris 18-20: jumlahKata.apply(masukan) mengalirkan " Belajar Function Composition di Java " melalui empat tahap dan menghasilkan 5. Tahap mana pun bisa juga dipakai sendiri — baris 21 membuktikan rapikan.apply(masukan) tetap berguna tanpa pipeline.
  4. Kenapa dipotong-potong? Setiap tahap bisa diuji terpisah, dipakai ulang di pipeline lain, dan diganti tanpa menyentuh tetangganya. Bila aturan berubah — misalnya pemisah menjadi koma — kita cukup mengganti tahap pecah; tiga tahap lain tidak berubah.

compose vs andThen — Kontras yang Terbukti #

Kedua penggabung sama-sama menerima Function dan menghasilkan Function. Aturan urutannya:

  • f.andThen(g) — jalankan f dulu, lalu g: hasil = g(f(x)). Mengingatkan kita membaca kiri-ke-kanan sesuai urutan penulisan.
  • f.compose(g) — jalankan g dulu, lalu f: hasil = f(g(x)). Sesuai matematika: f∘g dibaca “f sesudah g”.

Program berikut memakai dua Function bernama f dan g yang masing-masing mencatat jejak saat dieksekusi, sehingga urutan pemanggilan tidak lagi perlu dipercaya — cukup dibaca:

java JejakUrutan.java
import java.util.function.Function;

public class JejakUrutan {
    public static void main(String[] args) {
        Function<Integer, Integer> f = x -> {
            System.out.println("  f dieksekusi pada " + x);
            return x * 2;
        };
        Function<Integer, Integer> g = x -> {
            System.out.println("  g dieksekusi pada " + x);
            return x + 10;
        };

        System.out.println("f.compose(g).apply(5)");
        System.out.println("  hasil = " + f.compose(g).apply(5));

        System.out.println("f.andThen(g).apply(5)");
        System.out.println("  hasil = " + f.andThen(g).apply(5));
    }
}

Output yang Dihasilkan:

text
f.compose(g).apply(5)
  g dieksekusi pada 5
  f dieksekusi pada 15
  hasil = 30
f.andThen(g).apply(5)
  f dieksekusi pada 5
  g dieksekusi pada 10
  hasil = 20

Baca keluarannya perlahan — di sinilah letak bedanya:

  1. Bagian compose (baris keluaran 1-3): g dieksekusi pada 5 muncul lebih dulu. f.compose(g).apply(5) menjalankan g(5) → 15, menyerahkan 15 kepada f, dan f(15) menghasilkan 30. Rumusnya: f.compose(g) = f(g(x)) — argumen compose selalu berjalan lebih dulu.
  2. Bagian andThen (baris keluaran 4-6): urutan terbalik. f dieksekusi pada 5 lebih dulu: f(5) → 10, lalu g(10) → 20. Rumusnya: f.andThen(g) = g(f(x)) — pemilik andThen berjalan lebih dulu.
  3. Angka yang berbeda (30 lawan 20) membuktikan kedua penggabung bukan dua nama untuk satu hal: dengan f dan g yang sama, hasil akhirnya berbeda karena urutan eksekusinya berbeda.

Kapan compose Lebih Alami #

andThen unggul saat kita membangun pipeline dari hulu ke hilir — tahap ditulis sesuai urutan kerjanya. compose unggul saat kita sudah punya fungsi inti dan ingin melapisi pembersihan di depannya: parseInt.compose(pembersih) terbaca “parse, dengan pembersihan disiapkan lebih dulu” — fungsi inti tetap menjadi subjek kalimatnya. Keduanya bisa dirangkai panjang; untuk rantai lebih dari empat tahap, pertimbangkan memecah menjadi beberapa variabel bernama seperti sebelumValidasi dan sesudahValidasi agar pembaca kode tidak kehilangan arah di tengah deretan andThen.

Dengan komposisi di tangan, behavior kita kini bisa tumbuh menjadi alur kerja yang panjang namun tetap tersusun dari potongan kecil. Ada satu batasan penting yang belum kita bahas: potongan-potongan itu menyimpan akses ke variabel di sekitarnya — closure. Java mengizinkannya dengan satu syarat ketat bernama effectively final, dan melanggar syarat itu berakhir di pesan error compiler. Chapter berikutnya membedah aturan tersebut sampai ke akarnya.

Referensi #

Berkas Sumber

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

JejakUrutan.java

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

public class JejakUrutan {
    public static void main(String[] args) {
        Function<Integer, Integer> f = x -> {
            System.out.println("  f dieksekusi pada " + x);
            return x * 2;
        };
        Function<Integer, Integer> g = x -> {
            System.out.println("  g dieksekusi pada " + x);
            return x + 10;
        };

        System.out.println("f.compose(g).apply(5)");
        System.out.println("  hasil = " + f.compose(g).apply(5));

        System.out.println("f.andThen(g).apply(5)");
        System.out.println("  hasil = " + f.andThen(g).apply(5));
    }
}

PipelineTeks.java

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

public class PipelineTeks {
    public static void main(String[] args) {
        // Empat tahap kecil, masing-masing satu tanggung jawab
        Function<String, String> rapikan = String::trim;
        Function<String, String> kecilkan = String::toLowerCase;
        Function<String, String[]> pecah = teks -> teks.split(" ");
        Function<String[], Integer> hitung = kata -> kata.length;

        // Pipeline: keluaran setiap tahap mengalir ke tahap berikutnya
        Function<String, Integer> jumlahKata = rapikan
                .andThen(kecilkan)
                .andThen(pecah)
                .andThen(hitung);

        String masukan = "  Belajar Function Composition di Java  ";
        System.out.println("Jumlah kata : " + jumlahKata.apply(masukan));
        System.out.println("Tahap rapikan saja : [" + rapikan.apply(masukan) + "]");
    }
}