Selama perjalanan kita sejauh ini, kode Java yang kita tulis hampir selalu berbentuk perintah berurutan: buat variabel, ulangi daftar, ubah isi variabel, cetak hasil. Gaya tersebut disebut imperatif — kita menuliskan bagaimana komputer harus bekerja, langkah demi langkah. Sejak Java 8 (rilis 2014), Java membawa perangkat gaya fungsional — lambda expression, method reference, dan paket java.util.function — sehingga kita boleh menuliskan apa yang kita inginkan dan membiarkan pustaka standar yang mengurus detail langkahnya. Pada Section ini kita akan membangun fondasi gaya fungsional tersebut dari nol: dari functional interface, lambda, sampai pola desain yang memanfaatkannya.

Gaya Imperatif vs Gaya Deklaratif #

Perbedaan kedua gaya paling mudah dilihat pada tugas yang sama: menyaring nilai ujian yang lalu (nilai >= 70) dari sebuah daftar. Gaya imperatif memakai loop for eksplisit, variabel penampung yang isinya berubah-ubah, dan pernyataan if di dalam loop. Gaya deklaratif mendeskripsikan hasil akhirnya — “saring yang nilainya minimal 70” — melalui stream() dan filter(); loop, pengecekan, dan penyalinan hasil dikerjakan oleh pustaka.

import java.util.ArrayList;
import java.util.List;

public class DuaGayaFilter {
    public static void main(String[] args) {
        List<Integer> nilaiUjian = List.of(45, 78, 92, 61, 88, 30, 73);

        // Gaya imperatif: menulis BAGAIMANA, langkah demi langkah
        List<Integer> lulusImperatif = new ArrayList<>();
        for (int nilai : nilaiUjian) {
            if (nilai >= 70) {
                lulusImperatif.add(nilai);
            }
        }

        // Gaya deklaratif: mendeskripsikan APA yang diinginkan
        List<Integer> lulusDeklaratif = nilaiUjian.stream()
                .filter(nilai -> nilai >= 70)
                .toList();

        System.out.println("Imperatif  : " + lulusImperatif);
        System.out.println("Deklaratif : " + lulusDeklaratif);
    }
}

Output yang Dihasilkan:

text
Imperatif  : [78, 92, 88, 73]
Deklaratif : [78, 92, 88, 73]

Mari kita bedah kedua blok pada program di atas:

  1. Baris 10-16 (blok imperatif): kita menginisialisasi lulusImperatif sebagai ArrayList kosong, lalu mengendalikan sendiri iterasinya — satu per satu nilai diperiksa if (nilai >= 70), dan yang lolos ditambahkan dengan add. Seluruh mekanisme loop menjadi tanggung jawab kode kita.
  2. Baris 18-21 (blok deklaratif): nilaiUjian.stream() membuka aliran data, filter(nilai -> nilai >= 70) menerima predikat (kondisi) berbentuk lambda, dan toList() mengemas hasilnya. Kode membaca seperti spesifikasi masalah, bukan reka ulang mekanisme.
  3. Baris 23-24: kedua hasil dicetak dan terbukti identik — dua gaya, satu jawaban. Bagian stream() sendiri akan kita bahas tuntas pada Section Stream API; untuk sekarang, fokus kita adalah cara berpikirnya.

Motivasi: Komposisi dan Kejelasan #

Dua keunggulan utama gaya fungsional adalah komposisi (menyusun potongan kecil menjadi perilaku besar) dan kejelasan (kode membaca seperti deskripsi masalah). Contoh berikut menyuntikkan behavior ke dalam method pilih lewat parameter bertipe Predicate<Transaksi> — sebuah “kondisi” yang bisa dikombinasikan dengan operator and:

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

public class ManfaatKomposisi {

    record Transaksi(String kategori, int jumlah) {}

    // Behavior (kriteria) disuntik sebagai parameter, bukan ditulis ulang
    static List<Transaksi> pilih(List<Transaksi> data, Predicate<Transaksi> kriteria) {
        List<Transaksi> hasil = new ArrayList<>();
        for (Transaksi t : data) {
            if (kriteria.test(t)) {
                hasil.add(t);
            }
        }
        return hasil;
    }

    public static void main(String[] args) {
        List<Transaksi> transaksi = List.of(
                new Transaksi("makan", 25000),
                new Transaksi("transportasi", 12000),
                new Transaksi("makan", 40000),
                new Transaksi("hiburan", 60000));

        Predicate<Transaksi> kategoriMakan = t -> t.kategori().equals("makan");
        Predicate<Transaksi> jumlahBesar = t -> t.jumlah() > 30000;

        System.out.println("Transaksi makan      : " + pilih(transaksi, kategoriMakan).size());
        System.out.println("Transaksi > 30 ribu  : " + pilih(transaksi, jumlahBesar).size());
        System.out.println("Makan DAN > 30 ribu  : " + pilih(transaksi, kategoriMakan.and(jumlahBesar)).size());
    }
}

Output yang Dihasilkan:

text
Transaksi makan      : 2
Transaksi > 30 ribu  : 2
Makan DAN > 30 ribu  : 1

Tiga hal penting terjadi di berkas tersebut:

  1. Baris 10 (static List<Transaksi> pilih(...)): method pilih menerima daftar transaksi dan sebuah Predicate<Transaksi>. Method ini tidak tahu kriterianya apa — kriteria datang dari luar sebagai nilai (behavior sebagai data).
  2. Baris 25-26: dua kondisi dibuat sebagai variabel terpisah (kategoriMakan dan jumlahBesar), lalu dipakai berdiri sendiri maupun digabung dengan kategoriMakan.and(jumlahBesar) tanpa menulis method penyaring baru. Inilah komposisi: potongan kecil dirangkai menjadi potongan besar.
  3. Baris 28-30: tiga laporan berbeda dihasilkan dari satu method pilih. Pada gaya imperatif murni, kita umumnya menulis tiga loop yang hampir sama — tiga tempat yang harus diperiksa bila aturan berubah.

Java Tetap Bahasa Berorientasi Objek #

Java bukan Haskell atau Clojure. Java tetap OOP-first: seluruh lambda pada akhirnya adalah objek yang mengimplementasikan sebuah interface, kode tetap hidup di dalam class, dan tipe tetap diperiksa compiler javac pada saat kompilasi. Yang berubah sejak Java 8 adalah bahasa mengizinkan behavior diperlakukan sebagai nilai — disimpan di variabel, dikirim sebagai argumen, dan dikembalikan dari method — tanpa ritual anonymous class yang panjang.

Karena itu, gaya fungsional di Java paling tepat dipandang sebagai perangkat tambahan di tangan kita, bukan pengganti total: manipulasi koleksi, validasi berlapis, dan pembuatan pipeline data memang pas dengan gaya fungsional, sementara pemodelan domain bisnis tetap nyaman dengan class dan interface. Programmer Java produktif menguasai keduanya dan memilih sesuai masalah.

Peta Section Ini #

Berikut rute belajar kita pada Section ini:

  • Functional Interfaces — kontrak single abstract method (SAM) yang menjadi fondasi lambda.
  • Lambda Expressions & References — tiga cara menuliskan behavior: lambda, method reference, constructor reference.
  • Paket java.util.function — interface bawaan: Predicate, Function, Consumer, Supplier, beserta kombinasinya.
  • Komposisi & Ketetapan — compose/andThen, aturan effectively final, pure function, dan pola desain fungsional.

Semuanya berangkat dari satu konsep kunci: functional interface. Mari kita mulai dari sana.

Referensi #

Berkas Sumber

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

DuaGayaFilter.java

Unduh berkas
java
import java.util.ArrayList;
import java.util.List;

public class DuaGayaFilter {
    public static void main(String[] args) {
        List<Integer> nilaiUjian = List.of(45, 78, 92, 61, 88, 30, 73);

        // Gaya imperatif: menulis BAGAIMANA, langkah demi langkah
        List<Integer> lulusImperatif = new ArrayList<>();
        for (int nilai : nilaiUjian) {
            if (nilai >= 70) {
                lulusImperatif.add(nilai);
            }
        }

        // Gaya deklaratif: mendeskripsikan APA yang diinginkan
        List<Integer> lulusDeklaratif = nilaiUjian.stream()
                .filter(nilai -> nilai >= 70)
                .toList();

        System.out.println("Imperatif  : " + lulusImperatif);
        System.out.println("Deklaratif : " + lulusDeklaratif);
    }
}

ManfaatKomposisi.java

Unduh berkas
java
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

public class ManfaatKomposisi {

    record Transaksi(String kategori, int jumlah) {}

    // Behavior (kriteria) disuntik sebagai parameter, bukan ditulis ulang
    static List<Transaksi> pilih(List<Transaksi> data, Predicate<Transaksi> kriteria) {
        List<Transaksi> hasil = new ArrayList<>();
        for (Transaksi t : data) {
            if (kriteria.test(t)) {
                hasil.add(t);
            }
        }
        return hasil;
    }

    public static void main(String[] args) {
        List<Transaksi> transaksi = List.of(
                new Transaksi("makan", 25000),
                new Transaksi("transportasi", 12000),
                new Transaksi("makan", 40000),
                new Transaksi("hiburan", 60000));

        Predicate<Transaksi> kategoriMakan = t -> t.kategori().equals("makan");
        Predicate<Transaksi> jumlahBesar = t -> t.jumlah() > 30000;

        System.out.println("Transaksi makan      : " + pilih(transaksi, kategoriMakan).size());
        System.out.println("Transaksi > 30 ribu  : " + pilih(transaksi, jumlahBesar).size());
        System.out.println("Makan DAN > 30 ribu  : " + pilih(transaksi, kategoriMakan.and(jumlahBesar)).size());
    }
}