Jika Predicate menjawab “ya atau tidak”, Function<T, R> menjawab “menjadi apa?” — interface ini adalah mesin transformasi universal gaya fungsional: terima satu nilai bertipe T, keluarkan nilai baru bertipe R. Method abstraknya bernama apply, dan tipe masukan serta keluaran boleh berbeda sehingga Function<String, Integer> (urai teks menjadi angka) sama sahnya dengan Function<Double, Double> (terapkan pajak). Pada Chapter ini kita menyelami apply, sepupu dua-masukannya BiFunction, dua spesialisasinya UnaryOperator dan BinaryOperator, serta gerbang menuju dunia komposisi: method andThen.

Method apply sebagai Mesin Transformasi #

Cara memakai Function paling dasar adalah memanggil apply(nilai). Kompilator memastikan tipe argumen cocok dengan tipe masukan dan hasilnya membawa tipe keluaran — kesalahan tipe tertangkap sebelum program berjalan, sama seperti method biasa. Yang membedakan adalah nilai fungsi itu sendiri bisa berpindah: disimpan di variabel, dikirim sebagai argumen, diambil dari Map, atau diproduksi oleh method lain.

import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.UnaryOperator;

public class MesinTransformasi {
    public static void main(String[] args) {
        // Function dasar: satu masukan String, keluaran Integer
        Function<String, Integer> parser = Integer::parseInt;
        System.out.println(parser.apply("99"));

        // andThen: keluaran parser menjadi masukan lambda berikutnya
        Function<String, Integer> parserKaliDua = parser.andThen(n -> n * 2);
        System.out.println(parserKaliDua.apply("99"));

        // BiFunction: dua masukan, satu keluaran
        BiFunction<Integer, Integer, Integer> pangkat =
                (dasar, eksponen) -> (int) Math.pow(dasar, eksponen);
        System.out.println(pangkat.apply(2, 10));

        // UnaryOperator: masukan dan keluaran bertipe sama
        UnaryOperator<String> rapikan = s -> s.trim().toLowerCase();
        System.out.println("[" + rapikan.apply("  Halo Java  ") + "]");

        // BinaryOperator: dua masukan bertipe sama, keluaran bertipe sama
        BinaryOperator<String> gabung = (a, b) -> a + ", " + b;
        System.out.println(gabung.apply("imperatif", "deklaratif"));
    }
}

Output yang Dihasilkan:

text
99
198
1024
[halo java]
imperatif, deklaratif

Mari kita bedah kelima penggalan pada program di atas:

  1. Baris 9-10 (Function<String, Integer> parser): method reference Integer::parseInt mengisi apply(String). Panggilan parser.apply("99") menjalankan Integer.parseInt("99") dan mengembalikan 99 sebagai Integer.
  2. Baris 13-14 (parser.andThen(n -> n * 2)): andThen menggabungkan dua Function menjadi satu — keluaran parser mengalir langsung menjadi masukan n -> n * 2. Hasil "99" berubah menjadi 198 dalam satu panggilan parserKaliDua.apply("99"). Ini adalah contoh pertama komposisi, topik yang akan kita bedah penuh pada Chapter Function Composition.
  3. Baris 17-19 (BiFunction<Integer, Integer, Integer> pangkat): BiFunction<T, U, R> menerima dua nilai bertipe berbeda — di sini dasar dan eksponen — lalu apply(2, 10) mengembalikan 1024 (hasil Math.pow yang dipaksa kembali ke int).
  4. Baris 22-23 (UnaryOperator<String> rapikan): spesialisasi Function<T, T> ketika masukan dan keluaran bertipe sama. Rantai trim().toLowerCase() pada " Halo Java " menghasilkan "halo java" — tanda kurung siku pada keluaran hanya penanda bahwa spasi di awal dan akhir sudah bersih.
  5. Baris 26-27 (BinaryOperator<String> gabung): spesialisasi BiFunction<T, T, T> ketika ketiga tipe sama. Dua String masuk, satu String keluar — pola khas untuk operasi penggabungan.

Function dalam Perhitungan Nyata #

Kekuatan Function terlihat ketika transformasi kecil dirangkai menjadi alur bisnis. Program berikut menghitung tagihan: diskon (jika ada), pajak 11%, lalu pemformatan rupiah — masing-masing satu Function mandiri yang dirakit menjadi satu:

java HargaFinal.java
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.UnaryOperator;

public class HargaFinal {
    public static void main(String[] args) {
        UnaryOperator<Double> tambahPajak = harga -> harga * 1.11;
        Function<Double, String> formatRupiah = harga -> String.format("Rp%,.0f", harga);
        BiFunction<Double, Double, Double> terapkanDiskon =
                (harga, persen) -> harga * (1 - persen);

        // Rantai transformasi: pajak dulu, lalu format
        Function<Double, String> tagihan = tambahPajak.andThen(formatRupiah);
        System.out.println("Tanpa diskon : " + tagihan.apply(200000.0));

        // BiFunction menyala lebih dulu, hasilnya masuk ke rantai Function
        double setelahDiskon = terapkanDiskon.apply(200000.0, 0.2);
        System.out.println("Diskon 20%   : " + tagihan.apply(setelahDiskon));
    }
}

Output yang Dihasilkan:

text
Tanpa diskon : Rp222,000
Diskon 20%   : Rp177,600

Bedah alurnya:

  1. Baris 8-11: tiga mesin dideklarasikan terpisah — tambahPajak (UnaryOperator<Double>), formatRupiah (Function<Double, String>), dan terapkanDiskon (BiFunction<Double, Double, Double>). Setiap mesin bisa diuji sendiri dan dipakai ulang di perhitungan lain.
  2. Baris 14-15 (tambahPajak.andThen(formatRupiah)): rantai tagihan menerima Double harga dan mengeluarkan String siap cetak. Pemanggilan tagihan.apply(200000.0) mengalirkan 200000.0 → 222000.0 (pajak) → "Rp222,000" (format). Angka di sisi keluaran format memakai pemisah ribuan karena locale en_US aktif di JVM.
  3. Baris 18-19: terapkanDiskon.apply(200000.0, 0.2) menghasilkan 160000.0, lalu nilai itu masuk ke rantai tagihan yang sama — bukti bahwa BiFunction dan Function hidup dalam satu ekosistem: keluaran yang satu adalah masukan yang lain.
  4. Perhitungan pecahan: 200000.0 * 1.11 secara matematis biner tidak tepat (222000.00000000003), tetapi String.format("Rp%,.0f", ...) membulatkan ke Rp222,000 sehingga keluaran tetap rapi dan deterministik. Kita sengaja memformat di ujung pipeline, bukan membulatkan di tengah, agar presisi terjaga sepanjang rantai.

Dari apply ke Komposisi #

Seluruh contoh pada Chapter ini memakai apply sebagai pemicu dan andThen sebagai penggabung. Dua kata kunci itu sudah cukup untuk membangun pipeline berlapis, tetapi Java menyediakan satu lagi penggabung bernama compose — dan urutan keduanya berbeda. Membedakan f.andThen(g) dari f.compose(g) adalah pengetahuan yang sering tertukar bahkan oleh programmer berpengalaman, sehingga Chapter berikutnya mengabdikan seluruh bagiannya untuk kontras kedua method tersebut dengan bukti eksekusi yang bisa dilihat mata.

Referensi #

Berkas Sumber

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

HargaFinal.java

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

public class HargaFinal {
    public static void main(String[] args) {
        UnaryOperator<Double> tambahPajak = harga -> harga * 1.11;
        Function<Double, String> formatRupiah = harga -> String.format("Rp%,.0f", harga);
        BiFunction<Double, Double, Double> terapkanDiskon =
                (harga, persen) -> harga * (1 - persen);

        // Rantai transformasi: pajak dulu, lalu format
        Function<Double, String> tagihan = tambahPajak.andThen(formatRupiah);
        System.out.println("Tanpa diskon : " + tagihan.apply(200000.0));

        // BiFunction menyala lebih dulu, hasilnya masuk ke rantai Function
        double setelahDiskon = terapkanDiskon.apply(200000.0, 0.2);
        System.out.println("Diskon 20%   : " + tagihan.apply(setelahDiskon));
    }
}

MesinTransformasi.java

Unduh berkas
java
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.UnaryOperator;

public class MesinTransformasi {
    public static void main(String[] args) {
        // Function dasar: satu masukan String, keluaran Integer
        Function<String, Integer> parser = Integer::parseInt;
        System.out.println(parser.apply("99"));

        // andThen: keluaran parser menjadi masukan lambda berikutnya
        Function<String, Integer> parserKaliDua = parser.andThen(n -> n * 2);
        System.out.println(parserKaliDua.apply("99"));

        // BiFunction: dua masukan, satu keluaran
        BiFunction<Integer, Integer, Integer> pangkat =
                (dasar, eksponen) -> (int) Math.pow(dasar, eksponen);
        System.out.println(pangkat.apply(2, 10));

        // UnaryOperator: masukan dan keluaran bertipe sama
        UnaryOperator<String> rapikan = s -> s.trim().toLowerCase();
        System.out.println("[" + rapikan.apply("  Halo Java  ") + "]");

        // BinaryOperator: dua masukan bertipe sama, keluaran bertipe sama
        BinaryOperator<String> gabung = (a, b) -> a + ", " + b;
        System.out.println(gabung.apply("imperatif", "deklaratif"));
    }
}