Lambda İfadeleri ve Fonksiyonel Arayüzler
Java 8 ile gelen lambda'lar, Java'nın yüzünü değiştirdi. Eskiden 10 satırda yazdığın şeyi 1 satırda yazabilirsin. Ama sadece kısa yazmak değil mesele — düşünme biçimini değiştiriyor. Fonksiyonel programlama dünyasına açılan kapı burası.
Lambda Nedir?
Bir düşün: Bir arkadaşına "şu işi yap" diyeceksin. Eskiden ona 3 sayfalık talimat yazıyordun (anonymous inner class). Lambda ile sadece "şunu yap" diyorsun — tek satır.
Lambda = isimsiz fonksiyon. Bir metodu, bir değişken gibi taşıyabilir, parametre olarak geçirebilir, return edebilirsin.
Eskiden bir butona tıklama işlemi şöyle yazılırdı:
// Java 8 öncesi — anonymous inner class
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Tıklandı!");
}
});Lambda ile aynı şey:
// Java 8+ — lambda
button.addActionListener(e -> System.out.println("Tıklandı!"));7 satır → 1 satır. Aynı iş. Daha okunabilir.
Lambda Syntax
Lambda'nın genel syntax'ı:
(parametreler) -> { gövde }Ama Java çok esnek, birçok kısaltma var:
// Tam hali
(int a, int b) -> { return a + b; }
// Tip çıkarımı — derleyici tipleri bilir
(a, b) -> { return a + b; }
// Tek expression — return ve süslü parantez gereksiz
(a, b) -> a + b
// Tek parametre — parantez gereksiz
x -> x * 2
// Parametresiz
() -> System.out.println("Merhaba")
// Çok satırlı gövde — süslü parantez gerekir
(a, b) -> {
int toplam = a + b;
System.out.println("Toplam: " + toplam);
return toplam;
}💡 Kısa kural: Tek expression ise
{}vereturnyazma. Tek parametre ise()yazma. Birden fazla satır varsa{}zorunlu.
Fonksiyonel Arayüz (Functional Interface)
Lambda'nın çalışabilmesi için bir fonksiyonel arayüz gerekir. Bu, tek bir abstract metodu olan interface demek.
@FunctionalInterface
public interface Hesaplayici {
int hesapla(int a, int b);
}@FunctionalInterface anotasyonu zorunlu değil ama koyman iyi — derleyici kontrol eder, yanlışlıkla ikinci abstract metod eklersen hata verir.
Hesaplayici toplama = (a, b) -> a + b;
Hesaplayici cikarma = (a, b) -> a - b;
Hesaplayici carpma = (a, b) -> a * b;
System.out.println(toplama.hesapla(5, 3)); // 8
System.out.println(cikarma.hesapla(5, 3)); // 2
System.out.println(carpma.hesapla(5, 3)); // 15Lambda, fonksiyonel arayüzün abstract metodunun implementasyonunu sağlar. (a, b) -> a + b aslında hesapla metodunun gövdesidir.
Fonksiyonel Arayüz Kuralları
@FunctionalInterface
public interface Gecerli {
void yap(); // 1 abstract metod ✅
default void varsayilan() { } // Default metod sayılmaz
static void statik() { } // Static metod sayılmaz
String toString(); // Object'ten gelen sayılmaz
}
// @FunctionalInterface
public interface Gecersiz {
void yap();
void yapBaska(); // 2 abstract metod — fonksiyonel değil ❌
}Java'nın Hazır Fonksiyonel Arayüzleri
java.util.function paketi, en sık kullanılan 4 arayüzü sunar. Kendi interface'ini yazmadan önce bunlara bak:
1. Predicate\<T\> — Test Et, true/false Döndür
// boolean test(T t)
Predicate<String> uzunMu = s -> s.length() > 5;
Predicate<Integer> pozitifMi = n -> n > 0;
System.out.println(uzunMu.test("Java")); // false
System.out.println(uzunMu.test("JavaScript")); // true
System.out.println(pozitifMi.test(-3)); // falsePredicate'ler birleştirilebilir:
Predicate<Integer> pozitif = n -> n > 0;
Predicate<Integer> cift = n -> n % 2 == 0;
Predicate<Integer> pozitifVeCift = pozitif.and(cift);
Predicate<Integer> pozitifVeyaCift = pozitif.or(cift);
Predicate<Integer> negatif = pozitif.negate();
System.out.println(pozitifVeCift.test(4)); // true
System.out.println(pozitifVeCift.test(3)); // false
System.out.println(negatif.test(-5)); // true2. Function\<T, R\> — Dönüştür
// R apply(T t)
Function<String, Integer> uzunluk = s -> s.length();
Function<Integer, String> sayidanYazi = n -> "Sayı: " + n;
System.out.println(uzunluk.apply("Java")); // 4
System.out.println(sayidanYazi.apply(42)); // Sayı: 42Function'lar zincirlenebilir:
Function<String, String> buyukHarf = s -> s.toUpperCase();
Function<String, String> unlemEkle = s -> s + "!";
Function<String, String> bagir = buyukHarf.andThen(unlemEkle);
System.out.println(bagir.apply("merhaba")); // MERHABA!
Function<String, String> terstenBagir = buyukHarf.compose(unlemEkle);
System.out.println(terstenBagir.apply("merhaba")); // MERHABA!! (önce ünlem, sonra büyük harf)andThen: Önce bu, sonra o. compose: Önce o, sonra bu.
3. Consumer\<T\> — Tüket, Bir Şey Döndürme
// void accept(T t)
Consumer<String> yazdir = s -> System.out.println(s);
Consumer<String> buyukYazdir = s -> System.out.println(s.toUpperCase());
yazdir.accept("merhaba"); // merhaba
buyukYazdir.accept("merhaba"); // MERHABAConsumer da zincirlenebilir:
Consumer<String> logla = s -> System.out.println("[LOG] " + s);
Consumer<String> kaydet = s -> System.out.println("[SAVE] " + s);
Consumer<String> loglaVeKaydet = logla.andThen(kaydet);
loglaVeKaydet.accept("veri");
// [LOG] veri
// [SAVE] veri4. Supplier\<T\> — Üret, Parametre Alma
// T get()
Supplier<String> selamVer = () -> "Merhaba Dünya!";
Supplier<Double> rastgele = () -> Math.random();
Supplier<List<String>> bosliste = () -> new ArrayList<>();
System.out.println(selamVer.get()); // Merhaba Dünya!
System.out.println(rastgele.get()); // 0.7234... (rastgele)Supplier genellikle lazy initialization (tembel başlatma) için kullanılır:
// Nesne sadece ihtiyaç olunca oluşturulur
public static <T> T getOrDefault(T value, Supplier<T> defaultSupplier) {
return value != null ? value : defaultSupplier.get();
}
String isim = null;
String sonuc = getOrDefault(isim, () -> "Bilinmeyen");
System.out.println(sonuc); // BilinmeyenÖzet Tablo
| Arayüz | Metod | Parametre | Dönüş |
|---|---|---|---|
Predicate<T> | test(T) | T | boolean |
Function<T,R> | apply(T) | T | R |
Consumer<T> | accept(T) | T | void |
Supplier<T> | get() | — | T |
İki Parametreli Versiyonlar
// BiPredicate — iki parametre, boolean dönüş
BiPredicate<String, Integer> uzunlukKontrol =
(s, n) -> s.length() > n;
// BiFunction — iki parametre, bir dönüş
BiFunction<String, String, String> birlestir =
(a, b) -> a + " " + b;
// BiConsumer — iki parametre, void
BiConsumer<String, Integer> yazdir =
(isim, yas) -> System.out.println(isim + ": " + yas);
System.out.println(uzunlukKontrol.test("Java", 3)); // true
System.out.println(birlestir.apply("Merhaba", "Java")); // Merhaba Java
yazdir.accept("Ali", 25); // Ali: 25Method Reference (::)
Lambda'nın daha da kısa hali. Eğer lambda sadece mevcut bir metodu çağırıyorsa, method reference kullanabilirsin.
4 Tür Method Reference
1. Static method reference — `Sınıf::staticMetod`
// Lambda
Function<String, Integer> parse = s -> Integer.parseInt(s);
// Method reference
Function<String, Integer> parse = Integer::parseInt;2. Instance method reference (belirli nesne) — `nesne::metod`
String mesaj = "Merhaba Java";
// Lambda
Supplier<String> buyuk = () -> mesaj.toUpperCase();
// Method reference
Supplier<String> buyuk = mesaj::toUpperCase;3. Instance method reference (rastgele nesne) — `Sınıf::metod`
// Lambda — s parametresi üzerinde metod çağrılıyor
Function<String, String> buyuk = s -> s.toUpperCase();
// Method reference
Function<String, String> buyuk = String::toUpperCase;4. Constructor reference — `Sınıf::new`
// Lambda
Supplier<ArrayList<String>> listeOlustur = () -> new ArrayList<>();
// Method reference
Supplier<ArrayList<String>> listeOlustur = ArrayList::new;
// Parametreli constructor
Function<String, StringBuilder> sbOlustur = StringBuilder::new;Method Reference Örnekleri
List<String> isimler = List.of("ali", "veli", "ayse");
// Lambda
isimler.forEach(s -> System.out.println(s));
// Method reference — daha temiz
isimler.forEach(System.out::println);List<String> sayilar = List.of("3", "1", "4", "1", "5");
// Lambda
List<Integer> intList = sayilar.stream()
.map(s -> Integer.parseInt(s))
.toList();
// Method reference
List<Integer> intList = sayilar.stream()
.map(Integer::parseInt)
.toList();⚠️ Method reference her zaman kullanılmaz. Eğer lambda içinde ek işlem yapıyorsan (örn.
s -> s.trim().toUpperCase()), method reference kullanılamaz. Sadece doğrudan tek metod çağrısı yapıyorsan kullan.
Lambda ve Değişken Yakalama (Closure)
Lambda, dışarıdaki değişkenlere erişebilir. Ama bir kural var: effectively final olmalı.
String prefix = "Merhaba"; // effectively final — değeri değişmiyor
Consumer<String> selamla = isim -> System.out.println(prefix + " " + isim);
selamla.accept("Ali"); // Merhaba Aliint sayac = 0;
// DERLEME HATASI!
Consumer<String> say = s -> sayac++; // sayac değişiyor — effectively final değilEffectively final: Değişkene atandıktan sonra değeri hiç değişmiyorsa, final yazmasan bile effectively final'dır.
// DOĞRU — effectively final
String sehir = "İstanbul";
Supplier<String> al = () -> sehir;
// YANLIŞ — değer değişiyor, effectively final değil
String ulke = "Türkiye";
ulke = "Turkey"; // Bu satır yüzünden lambda'da kullanılamaz
// Supplier<String> al = () -> ulke; // HATA!Neden bu kural var? Lambda farklı bir thread'de çalışabilir. Değişen bir değişkene iki thread'den erişmek race condition yaratır. Java bunu baştan engelliyor.
Workaround — Dizi veya AtomicInteger
// Dizi ile (referans final, içerik değişebilir)
int[] sayac = {0};
Consumer<String> say = s -> sayac[0]++;
// AtomicInteger ile (daha temiz)
AtomicInteger atomicSayac = new AtomicInteger(0);
Consumer<String> say2 = s -> atomicSayac.incrementAndGet();Lambda Kullanım Alanları
Koleksiyonları Sıralama
List<String> isimler = new ArrayList<>(List.of("Zeynep", "Ali", "Burak"));
// Eskiden
Collections.sort(isimler, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
});
// Lambda ile
isimler.sort((a, b) -> a.compareTo(b));
// Method reference ile
isimler.sort(String::compareTo);
// Comparator helper'ları ile
isimler.sort(Comparator.naturalOrder());
isimler.sort(Comparator.reverseOrder());Nesneleri Özel Sıralama
List<Ogrenci> ogrenciler = List.of(
new Ogrenci("Ali", 85),
new Ogrenci("Zeynep", 92),
new Ogrenci("Burak", 78)
);
// Nota göre sırala
ogrenciler.sort(Comparator.comparingInt(Ogrenci::getNot));
// Nota göre ters sırala
ogrenciler.sort(Comparator.comparingInt(Ogrenci::getNot).reversed());
// Önce nota, sonra isme göre
ogrenciler.sort(
Comparator.comparingInt(Ogrenci::getNot)
.thenComparing(Ogrenci::getIsim)
);Koleksiyonlarda Filtreleme ve İterasyon
List<Integer> sayilar = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));
// removeIf — Predicate alır
sayilar.removeIf(n -> n % 2 == 0); // Çiftleri kaldır
System.out.println(sayilar); // [1, 3, 5]
// forEach — Consumer alır
sayilar.forEach(n -> System.out.println("Sayı: " + n));
// replaceAll — UnaryOperator alır
sayilar.replaceAll(n -> n * 10);
System.out.println(sayilar); // [10, 30, 50]Map İşlemleri
Map<String, Integer> puanlar = new HashMap<>();
puanlar.put("Ali", 85);
puanlar.put("Veli", 90);
// forEach
puanlar.forEach((isim, puan) ->
System.out.println(isim + ": " + puan));
// computeIfAbsent — yoksa hesapla ve ekle
puanlar.computeIfAbsent("Ayşe", isim -> 75);
// merge — birleştir
puanlar.merge("Ali", 5, (eski, ek) -> eski + ek);
System.out.println(puanlar.get("Ali")); // 90
// replaceAll
puanlar.replaceAll((isim, puan) -> puan + 10);Kendi Fonksiyonel Arayüzün
Hazır arayüzler yetmezse kendi yaz:
@FunctionalInterface
public interface TriFunction<A, B, C, R> {
R apply(A a, B b, C c);
}TriFunction<Integer, Integer, Integer, Integer> topla3 =
(a, b, c) -> a + b + c;
System.out.println(topla3.apply(1, 2, 3)); // 6@FunctionalInterface
public interface Validator<T> {
boolean validate(T t);
// Default metod — validator'ları birleştir
default Validator<T> and(Validator<T> other) {
return t -> this.validate(t) && other.validate(t);
}
}Validator<String> bosDegilMi = s -> !s.isEmpty();
Validator<String> uzunMu = s -> s.length() >= 3;
Validator<String> gecerliMi = bosDegilMi.and(uzunMu);
System.out.println(gecerliMi.validate("")); // false
System.out.println(gecerliMi.validate("ab")); // false
System.out.println(gecerliMi.validate("abc")); // trueLambda vs Anonymous Inner Class
| Özellik | Lambda | Anonymous Inner Class |
|---|---|---|
| Syntax | Kısa | Uzun |
this referansı | Dış sınıfı gösterir | Kendi instance'ını gösterir |
| Fonksiyonel arayüz | Zorunlu (tek abstract metod) | Herhangi bir interface/class |
| Performans | Daha iyi (invokedynamic) | Yeni class dosyası üretir |
public class Ornek {
String mesaj = "Dış sınıf";
void test() {
// Anonymous inner class — this = inner class
Runnable r1 = new Runnable() {
String mesaj = "İç sınıf";
public void run() {
System.out.println(this.mesaj); // "İç sınıf"
}
};
// Lambda — this = dış sınıf (Ornek)
Runnable r2 = () -> {
System.out.println(this.mesaj); // "Dış sınıf"
};
}
}💡 Lambda'da `this`, her zaman dış sınıfı (enclosing class) gösterir. Anonymous inner class'ta ise kendi instance'ını gösterir. Bu önemli bir fark.
Özet
Lambda = isimsiz fonksiyon,
(parametreler) -> gövdesyntax'ı ile yazılırFonksiyonel arayüz = tek abstract metodu olan interface — lambda'nın hedefi
Predicate (test), Function (dönüştür), Consumer (tüket), Supplier (üret) — 4 temel fonksiyonel arayüz
Method reference (
::) = lambda'nın kısa hali, sadece mevcut metod çağrısı yapıyorsan kullanEffectively final kuralı: lambda dış değişkene erişebilir ama değiştiremez
Lambda, Stream API ile birleştiğinde gerçek gücünü gösterir — bir sonraki ders!
AI Asistan
Sorularını yanıtlamaya hazır