Lambda & Stream API
Functional interface, ekspresi lambda, method reference, Stream (filter, map, reduce, collect), dan Optional.
Lambda: fungsi singkat
Sejak Java 8, kamu bisa menulis potongan perilaku sebagai nilai. Ekspresi lambda adalah method tanpa nama dengan sintaks (parameter) -> hasil.
// cara lama: kelas anonim
Comparator<String> lama = new Comparator<String>() {
public int compare(String a, String b) { return a.length() - b.length(); }
};
// lambda
Comparator<String> baru = (a, b) -> a.length() - b.length();
Variasi penulisan:
x -> x * 2 // satu parameter, tanpa kurung
(a, b) -> a + b // dua parameter
() -> System.out.println("hi") // tanpa parameter
(a, b) -> { // beberapa baris: pakai kurung kurawal + return
int s = a + b;
return s * 2;
}
Functional interface
Lambda bisa dipakai di mana pun ada functional interface, yaitu interface dengan satu method abstrak. Yang sering dipakai (paket java.util.function):
| Interface | Bentuk | Contoh |
|---|---|---|
Predicate<T> | T → boolean | x -> x > 5 |
Function<T,R> | T → R | s -> s.length() |
Consumer<T> | T → (tidak ada) | s -> System.out.println(s) |
Supplier<T> | () → T | () -> "baru" |
Runnable | () → (tidak ada) | () -> doWork() |
Predicate<Integer> genap = n -> n % 2 == 0;
System.out.println(genap.test(4)); // true
Method reference
Jika lambda hanya memanggil satu method, tulis lebih ringkas dengan :::
nama.forEach(n -> System.out.println(n));
nama.forEach(System.out::println); // sama
nama.stream().map(String::toUpperCase); // s -> s.toUpperCase()
Stream API
Stream adalah aliran data yang diproses dengan rangkaian operasi (pipeline) tanpa mengubah sumbernya. Strukturnya: sumber → operasi perantara → operasi akhir.
List<Integer> angka = List.of(1, 2, 3, 4, 5, 6);
List<Integer> kuadratGenap = angka.stream()
.filter(n -> n % 2 == 0) // saring: 2, 4, 6
.map(n -> n * n) // ubah: 4, 16, 36
.toList(); // kumpulkan (Java 16+)
System.out.println(kuadratGenap); // [4, 16, 36]
| Operasi | Jenis | Fungsi |
|---|---|---|
filter(Predicate) | perantara | menyaring elemen |
map(Function) | perantara | mengubah tiap elemen |
sorted() | perantara | mengurutkan |
distinct(), limit(n) | perantara | buang duplikat, ambil n pertama |
forEach(Consumer) | akhir | menjalankan aksi per elemen |
count() | akhir | jumlah elemen |
sum(), average(), max() | akhir (stream angka) | agregasi |
reduce(...) | akhir | menggabung jadi satu nilai |
collect(Collectors...) | akhir | mengumpulkan ke List, Set, Map, dll |
Operasi perantara bersifat malas (lazy): tidak ada yang dijalankan sampai ada operasi akhir.
Contoh lebih nyata
record Produk(String nama, double harga, String kategori) { }
List<Produk> daftar = List.of(
new Produk("Pensil", 3000, "alat"),
new Produk("Buku", 15000, "alat"),
new Produk("Roti", 8000, "makanan"));
double totalAlat = daftar.stream()
.filter(p -> p.kategori().equals("alat"))
.mapToDouble(Produk::harga)
.sum(); // 18000.0
Map<String, List<Produk>> perKategori = daftar.stream()
.collect(Collectors.groupingBy(Produk::kategori));
String nama = daftar.stream().map(Produk::nama).collect(Collectors.joining(", "));
Optional
Optional<T> membungkus nilai yang mungkin tidak ada, sebagai pengganti null yang rawan NullPointerException.
Optional<Produk> termurah = daftar.stream().min(Comparator.comparingDouble(Produk::harga));
termurah.ifPresent(p -> System.out.println(p.nama()));
String hasil = termurah.map(Produk::nama).orElse("kosong");
Catatan penting
- Stream hanya bisa dipakai sekali.
- Variabel lokal yang dipakai di dalam lambda harus effectively final (tidak diubah setelah diisi).
- Untuk loop sederhana,
forbiasa kadang lebih jelas; pakai Stream saat memperjelas niat (saring → ubah → kumpulkan).