Anonymous class pernah menjadi satu-satunya cara Java mengirim behavior ke dalam method — dan lima baris ritual (new Comparator<String>() { ... }) demi satu baris logika sungguhan. Java 8 menggantikan ritual itu dengan lambda expression: sintaks (parameter) -> tubuh yang langsung mengimplementasikan method abstrak tunggal dari functional interface. Kita sudah mengenal rumahnya pada Chapter sebelumnya; sekarang kita belajar semua bentuk penghuninya, dari yang tanpa parameter sampai blok multi-baris, lalu membandingkannya langsung dengan anonymous class.

Empat Bentuk Dasar Lambda #

Bentuk lambda mengikuti kebutuhan method abstraknya: parameter bisa tidak ada, bisa satu, bisa banyak; tubuh bisa berupa satu ekspresi atau blok penuh dengan return. Sintaks intinya selalu sama — daftar parameter, tanda panah ->, lalu tubuh. Compiler javac menyimpulkan tipe parameter dari functional interface yang menjadi target type, sehingga kita nyaris tidak pernah menulis tipe parameter secara eksplisit.

import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

public class BentukLambda {
    public static void main(String[] args) {
        // Bentuk 1: tanpa parameter, kurung wajib dan kosong
        Supplier<String> sapaan = () -> "Selamat datang!";
        System.out.println(sapaan.get());

        // Bentuk 2: satu parameter, kurung boleh dibuang
        Function<String, Integer> panjang = teks -> teks.length();
        System.out.println(panjang.apply("fungsional"));

        // Bentuk 3: dua parameter, kurung dan koma wajib
        BiFunction<Integer, Integer, Integer> jumlah = (a, b) -> a + b;
        System.out.println(jumlah.apply(7, 5));

        // Bentuk 4: blok statement, return eksplisit dan titik koma
        Function<Integer, String> status = nilai -> {
            if (nilai >= 70) {
                return "LULUS";
            }
            return "REMEDIAL";
        };
        System.out.println(status.apply(85));
        System.out.println(status.apply(60));
    }
}

Output yang Dihasilkan:

text
Selamat datang!
10
12
LULUS
REMEDIAL

Mari kita bedah keempat bentuk pada program di atas:

  1. Baris 9 (() -> "Selamat datang!"): method abstrak Supplier.get() tidak menerima parameter, jadi kurung harus ditulis dan dibiarkan kosong. Tubuh berupa ekspresi "Selamat datang!" — nilai ekspresi menjadi nilai kembalian tanpa kata kunci return.
  2. Baris 13 (teks -> teks.length()): dengan tepat satu parameter, kurung boleh dibuang sehingga teks terbaca seperti variabel biasa. Compiler tahu teks bertipe String karena target type-nya Function<String, Integer> — method apply menerima String.
  3. Baris 17 ((a, b) -> a + b): dua parameter wajib berkurung dan dipisah koma. Tipe a dan b disimpulkan sebagai Integer dari BiFunction<Integer, Integer, Integer>.
  4. Baris 21-28 (nilai -> { ... return ...; }): bila tubuh butuh percabangan, kita pakai blok dengan kurung kurawal. Di dalam blok, aturan method biasa berlaku — return ditulis eksplisit dan setiap statement diakhiri titik koma.
  5. Baris 29-30: status.apply(85) dan status.apply(60) menjalankan blok yang sama dengan masukan berbeda — lambda adalah nilai yang bisa dipanggil berulang kali seperti method.

Menggantikan Anonymous Class #

Sebelum ada lambda, Comparator dan Runnable diimplementasikan dengan anonymous class: deklarasi tipe, kata kunci new, peng-override, dan tubuh — padahal yang penting hanya tubuhnya. Program berikut menjalankan tugas yang sama dua kali, pertama dengan anonymous class, lalu dengan lambda:

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

public class AnonimVsLambda {
    public static void main(String[] args) {
        List<String> nama = new ArrayList<>(List.of("Kurniawan", "Budi", "Siti"));

        // Versi 1: anonymous class (gaya sebelum Java 8)
        Comparator<String> panjangAsc = new Comparator<String>() {
            @Override
            public int compare(String a, String b) {
                return Integer.compare(a.length(), b.length());
            }
        };
        nama.sort(panjangAsc);
        System.out.println("Anonymous class : " + nama);

        // Versi 2: lambda dengan makna yang sama persis
        nama.sort((a, b) -> Integer.compare(b.length(), a.length()));
        System.out.println("Lambda          : " + nama);

        // Runnable: anonymous class vs lambda
        Runnable tugasAnonim = new Runnable() {
            @Override
            public void run() {
                System.out.println("Tugas berjalan (anonymous)");
            }
        };
        tugasAnonim.run();

        Runnable tugasLambda = () -> System.out.println("Tugas berjalan (lambda)");
        tugasLambda.run();
    }
}

Output yang Dihasilkan:

text
Anonymous class : [Budi, Siti, Kurniawan]
Lambda          : [Kurniawan, Budi, Siti]
Tugas berjalan (anonymous)
Tugas berjalan (lambda)

Perhatikan tiga hal dari hasil eksekusi tersebut:

  1. Baris 11-18 (anonymous class): lima baris hanya untuk mengatakan “bandingkan panjang string”. Kelas tanpa nama dibuat saat runtime sebagai turunan Comparator<String> — objek sungguhan dengan alokasi memori dan method compare yang di-override.
  2. Baris 25 (lambda): (a, b) -> Integer.compare(b.length(), a.length()) menyatakan hal yang sama dalam satu baris. JVM tidak membuat class turunan baru per penulisan lambda; implementasi internalnya berbentuk invokedynamic — mekanisme yang menghasilkan objek fungsional secara efisien pada pemanggilan pertama.
  3. Baris 28-39 (Runnable): pola yang sama berlaku untuk Runnable. Kedua versi menghasilkan keluaran identik karena keduanya mengimplementasikan method abstrak yang sama; yang berubah hanya jumlah huruf yang kita ketik dan tingkat kebisingan visual saat membaca kode.

Perbandingan ini menjelaskan mengapa lambda cepat diadopsi: bukan karena kemampuan baru (anonymous class tetap bisa melakukannya), melainkan karena biaya penulisan dan pembacaan turun drastis. Aturan praktisnya sederhana — jika sebuah anonymous class hanya berisi satu method dan tubuhnya pendek, ganti dengan lambda; jika anonymous class menyimpan state (variabel anggota) atau meng-override lebih dari satu method, class biasa tetap pilihan yang tepat.

Inferensi Tipe dan Var #

Lambda tidak pernah berdiri tanpa konteks: compiler selalu memerlukan target type sebelum bisa menyimpulkan tipe parameter. Karena itu pernyataan var banding = (a, b) -> ...; tanpa informasi lebih ditolak compiler — var tidak membawa tipe interface, sehingga compiler tidak punya method abstrak untuk dipetakan. Sebaliknya, kombinasi Comparator<String> banding = (a, b) -> ... atau var banding = Comparator.<String>comparingInt(String::length); berjalan baik karena tipe target hadir secara eksplisit.

Kita sekarang bisa menulis behavior dalam bentuk paling ringkas. Chapter berikutnya mempersingkat lagi: saat tubuh lambda hanya memanggil satu method yang sudah ada, method reference seperti String::length menggantikan seluruh lambda tanpa kehilangan makna.

Referensi #

Berkas Sumber

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

AnonimVsLambda.java

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

public class AnonimVsLambda {
    public static void main(String[] args) {
        List<String> nama = new ArrayList<>(List.of("Kurniawan", "Budi", "Siti"));

        // Versi 1: anonymous class (gaya sebelum Java 8)
        Comparator<String> panjangAsc = new Comparator<String>() {
            @Override
            public int compare(String a, String b) {
                return Integer.compare(a.length(), b.length());
            }
        };
        nama.sort(panjangAsc);
        System.out.println("Anonymous class : " + nama);

        // Versi 2: lambda dengan makna yang sama persis
        nama.sort((a, b) -> Integer.compare(b.length(), a.length()));
        System.out.println("Lambda          : " + nama);

        // Runnable: anonymous class vs lambda
        Runnable tugasAnonim = new Runnable() {
            @Override
            public void run() {
                System.out.println("Tugas berjalan (anonymous)");
            }
        };
        tugasAnonim.run();

        Runnable tugasLambda = () -> System.out.println("Tugas berjalan (lambda)");
        tugasLambda.run();
    }
}

BentukLambda.java

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

public class BentukLambda {
    public static void main(String[] args) {
        // Bentuk 1: tanpa parameter, kurung wajib dan kosong
        Supplier<String> sapaan = () -> "Selamat datang!";
        System.out.println(sapaan.get());

        // Bentuk 2: satu parameter, kurung boleh dibuang
        Function<String, Integer> panjang = teks -> teks.length();
        System.out.println(panjang.apply("fungsional"));

        // Bentuk 3: dua parameter, kurung dan koma wajib
        BiFunction<Integer, Integer, Integer> jumlah = (a, b) -> a + b;
        System.out.println(jumlah.apply(7, 5));

        // Bentuk 4: blok statement, return eksplisit dan titik koma
        Function<Integer, String> status = nilai -> {
            if (nilai >= 70) {
                return "LULUS";
            }
            return "REMEDIAL";
        };
        System.out.println(status.apply(85));
        System.out.println(status.apply(60));
    }
}