Belajar OOP dengan Java - Functional Programming (Lambdas & Streams)
Series/Belajar OOP/Episode 13
Episode 13 of 23

Belajar OOP dengan Java - Functional Programming (Lambdas & Streams)

Menggabungkan OOP dengan gaya fungsional: lambda expression, functional interfaces Function, Predicate, Supplier dan Consumer, Stream API dengan map/filter/collect, Optional untuk menghindari null, serta method references yang membuat pipeline data ringkas dan deklaratif.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Java sering dicap "terlalu banyak boilerplate" — dan untuk Java lama, itu benar. Namun sejak Java 8, Java punya dua senjata yang mengubah gaya penulisannya: lambdas dan Stream API. Episode ini menggabungkan OOP yang sudah kalian kuasai dengan gaya fungsional — bukan menggantikan, melainkan memperkaya.

Mengapa penting? Karena hampir semua kode produksi Java modern memproses data dalam pipeline: ambil list, filter, ubah, kumpulkan. Stream API membuat pipeline ini deklaratif — kalian menyatakan apa yang ingin dicapai, bukan bagaimana caranya dengan loop dan variable sementara. Hasilnya: lebih pendek, lebih sedikit bug, lebih mudah dibaca.

Lambda Expression

Dari Anonymous Class ke Lambda

Sebelum Java 8, memberi perilaku berarti menulis class anonim — verbose. Perbandingan langsung:

JavaSebelum lambda: anonymous class
Runnable tugas = new Runnable() {
    @Override
    public void run() {
        System.out.println("Menjalankan tugas");
    }
};
JavaSetelah lambda
Runnable tugas = () -> System.out.println("Menjalankan tugas");

Lambda () -> ... adalah singkatan untuk class anonim yang hanya punya satu method. Simbol -> memisahkan parameter (kiri) dari badan (kanan):

java
(int a, int b) -> a + b          // dua parameter
x -> x * 2                       // satu parameter (tanpa tanda kurung)
() -> System.out.println("halo") // tanpa parameter

Functional Interfaces

Lambda hanya bisa dipakai dengan functional interface — interface dengan tepat satu method abstract. Java menyediakan yang paling sering dipakai di java.util.function:

InterfaceMethodPeran
Function<T, R>R apply(T)Ubah T menjadi R
Predicate<T>boolean test(T)Uji kondisi
Supplier<T>T get()Produksi nilai tanpa input
Consumer<T>void accept(T)Konsumsi nilai, tanpa hasil
JavaEmpat functional interface utama
import java.util.function.*;
 
Function<String, Integer> panjang = s -> s.length();
Predicate<Integer> genap = n -> n % 2 == 0;
Supplier<String> waktu = () -> "2026-08-16";
Consumer<String> cetak = s -> System.out.println(s);
 
System.out.println(panjang.apply("Java")); // 4
System.out.println(genap.test(10));        // true
System.out.println(waktu.get());           // 2026-08-16
cetak.accept("Selesai");

Konsep kunci yang sering mengejutkan: behavior bisa disimpan sebagai nilai. Predicate<Integer> adalah object yang membawa logika n % 2 == 0. Inilah cara OOP dan functional bertemu — lambda adalah object, dan bisa dipakai untuk polymorphism (episode 8) serta strategi (episode 15).

Stream API

Dari Loop ke Pipeline

Masalah klasik yang diselesaikan Streams. Sebelumnya:

JavaGaya imperatif dengan loop
import java.util.ArrayList;
import java.util.List;
 
List<String> hasil = new ArrayList<>();
for (String nama : namaSemua) {
    if (nama.startsWith("A")) {
        hasil.add(nama.toUpperCase());
    }
}

Dengan Stream API:

JavaGaya deklaratif dengan Stream
import java.util.List;
import java.util.stream.Collectors;
 
List<String> hasil = namaSemua.stream()
        .filter(nama -> nama.startsWith("A"))
        .map(String::toUpperCase)
        .collect(Collectors.toList());

Bandingkan: yang kedua menyatakan apa (filter yang diawali A, lalu ubah besar) — bukan bagaimana (loop, variabel hasil, add manual). Komponen pipeline:

  • filter — menyaring dengan Predicate.
  • map — mengubah setiap elemen dengan Function.
  • collect — mengumpulkan hasil ke struktur (biasanya List/Set/Map).

Chain Operasi dan Lazy Evaluation

Pipeline Stream adalah lazy: operasi tidak dieksekusi sampai ada operasi terminal (seperti collect, forEach, count). Operasi antara (filter, map) hanya dicatat sebagai rencana.

JavaPipeline lengkap
import java.util.List;
 
public class Main {
    public static void main(String[] args) {
        List<Integer> angka = List.of(1, 2, 3, 4, 5, 6);
 
        int jumlahGenapKuadrat = angka.stream()
                .filter(n -> n % 2 == 0)     // Predicate
                .map(n -> n * n)             // Function
                .mapToInt(Integer::intValue) // ke int stream
                .sum();
 
        System.out.println(jumlahGenapKuadrat); // 4 + 16 + 36 = 56
    }
}

Karena lazy, Java bisa memproses hanya elemen yang benar-benar dibutuhkan — misal dengan limit(3) dan findFirst(), stream tidak memproses seluruh data. Ini yang membuat Stream efisien untuk koleksi besar.

Optional: Menghindari Null

Optional adalah wadah yang bisa berisi nilai atau kosong — menggantikan cara lama mengembalikan null (dan menunggu NullPointerException):

JavaOptional menghindari null
import java.util.Optional;
 
public class Penyimpanan {
    public static Optional<String> cariNama(String kunci) {
        if ("alya".equals(kunci)) {
            return Optional.of("Alya");
        }
        return Optional.empty();
    }
 
    public static void main(String[] args) {
        Optional<String> hasil = cariNama("budi");
 
        System.out.println(hasil.orElse("Tidak ditemukan"));
        hasil.ifPresent(nama -> System.out.println("Halo " + nama));
    }
}

orElse("Tidak ditemukan") memberikan default tanpa menulis if, dan ifPresent(...) menjalankan aksi hanya jika nilai ada. Contract-nya eksplisit: method yang mengembalikan Optional menyatakan bahwa hasil bisa kosong — pemanggil dipaksa berpikir.

Method References

Kadang lambda cuma memanggil method yang sudah ada — persis String::toUpperCase. Method references memendekkannya:

BentukArti
String::lengths -> s.length()
System.out::printlnx -> System.out.println(x)
Nama::newx -> new Nama(x)
JavaMethod reference
namaSemua.stream()
        .filter(nama -> nama.startsWith("A"))
        .map(String::toUpperCase)
        .forEach(System.out::println);

String::toUpperCase memanggil method tanpa argumen pada setiap elemen; System.out::println adalah Consumer. Method references lebih pendek sekaligus lebih jelas — gunakan saat lambda hanya mendelegasikan satu panggilan.

Warning

Jangan memakai Optional sebagai field atau parameter method — itu bukan tujuannya. Optional dirancang sebagai return type untuk menyatakan "bisa kosong". Menggunakannya di tempat lain menambah kompleksitas tanpa manfaat (detail di episode 18).

Kapan Stream, Kapan Loop?

Stream bukan pengganti loop universal. Panduannya:

  • Gunakan Stream: transformasi data (filter + map + collect), pipeline bertingkat, operasi koleksi besar.
  • Gunakan loop biasa: butuh break/continue kompleks, mengubah elemen sambil iterasi, melacak indeks dengan cara yang tidak nyaman di stream, atau kode lebih jelas dengan state berubah.

Jangan fanatik — yang penting kode bisa dibaca. Episode 21 akan mengajarkan rasa kapan gaya mana yang paling jelas.

Tip

Stream terasa aneh pertama kali justru karena ia mengubah cara berpikir: dari "bagaimana memindahkan data" ke "transformasi apa yang saya inginkan". Latihan terbaik: tulis satu logika dengan loop, lalu tulis ulang dengan stream, dan bandingkan mana yang lebih mudah dibaca.

Penutup

Inti yang harus dibawa pulang:

  • Lambda mempersingkat anonymous class; -> memisahkan parameter dan badan.
  • Functional interface utama: Function, Predicate, Supplier, Consumer — behavior sebagai nilai.
  • Stream membuat pipeline deklaratif: filtermapcollect; lazy hingga operasi terminal.
  • Optional menyatakan "bisa kosong" secara eksplisit, menggantikan null yang rawan error.
  • Method references (String::toUpperCase) memendekkan lambda yang hanya mendelegasikan.

Di episode 14 selanjutnya kita akan membahas SOLID principles — Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, dan Dependency Inversion plus dependency injection. Sampai jumpa di episode 14!

Belajar OOP dengan Java - Functional Programming (Lambdas & Streams) | Belajar OOP