Lambda ve Fonksiyonel Arayüzler — Lambda Neyi Yakalar?
Önce şunu oku: Stack, Heap ve Pass-by-Value
30 saniyede özet
Lambda, tek işi olan bir arayüzü kısa yoldan yazmaktır. Kullandığı yerel değişkenin fotoğrafını çeker, bu yüzden o değişken sonradan değişmemeli. int[] hilesiyle derleyiciyi susturmak sorunu çözmez, saklar.
Bir döngüde üç görev oluşturdun, her biri kendi sıra numarasını yazacaktı. Derleyici i’ye itiraz edince int[] box ile susturdun. Görevler çalıştı ve üçü de 2 yazdı.
Adım adım oku
- Lambda, kullandığı yerel değişkenin o anki değerinin fotoğrafını çeker: i = 1.
- Fotoğraf sonradan değişmeyeceği için değişkenin de değişmemesi gerekir; derleyici i = 2'ye izin vermez.
- Bir diziyi ya da nesneyi yakaladığında ise fotoğraf değil, pencere alır: kutunun kendisine bakar.
- Kutunun içi değişirse lambda da çalıştığı anda yeni hâlini görür. Üç görevin üçü de 2 yazar.
-
Bayt: Döngüde üç görev kurdum, her biri kendi sıra numarasını yazacaktı.
-
Sen: Derleyici
i'ye itiraz etti. Ben de sayıyı bir dizi kutuya koyup susturdum. -
Bayt: Görevler çalıştı ve üçü de 2 yazdı?!
-
Bayt: Lambda bazen fotoğraf çeker, bazen pencereden bakar. Sen ona pencere verdin.
Lambda aslında ne?
Lambda, “şunu yap” diye küçük bir iş parçasını başka bir koda vermenin kısa yoludur. Arka planda tek metodu olan bir arayüzü, yani bir functional interfaceTek bir soyut metodu olan arayüz. Lambda ve method reference yalnızca böyle bir arayüzün yerine geçebilir.Default metotlar, static metotlar ve Object'ten gelen equals gibi metotların yeniden bildirimi soyut metot sayımına girmez.Sözlükte gör →’i yerinde doldurur.
| Arayüz | Metot | Ne zaman |
|---|---|---|
Function<T, R> | R apply(T) | Dönüştür |
Supplier<T> | T get() | Üret, ertele |
Consumer<T> | void accept(T) | Tüket, yan etki |
Predicate<T> | boolean test(T) | Süz |
Lambda yalnızca var olan bir metodu çağırıyorsa method referenceYalnızca var olan bir metodu çağıran lambdanın kısa yazımı: String::length, Integer::parseInt, ArrayList::new.Alıcısı bağlı olan türde (obj::m, System.out::println) obj, referans oluşturulurken değerlendirilir; null ise hemen NullPointerException fırlar.Sözlükte gör → daha okunur: s -> s.length() yerine String::length.
Kafam karıştı, daha basit anlat
Lambda, başka bir koda verdiğin küçük bir tariftir: “bunu al, şunu yap”. Java bu tarifi tek metotlu bir arayüzün içine koyar.
`Comparator<T>` arayüzünde hem `compare` hem de `equals` soyut metot olarak bildirilmiş. Yine de neden bir functional interface sayılır?
Her method reference'ı türüne göre ayır.
Yakalama: fotoğraf, pencere değil
for (int i = 0; i < 3; i++) tasks.add(() -> print(i)); — bu satır derlenir mi? Cevabı göster
Derlenmez. Lambda i’nin değerini kopyalar; i ise i++ ile değişmeye devam eder. Derleyici, kopyası ile aslı ayrışacak bir değişkeni yakalamana izin vermez.
Lambda, kullandığı yerel değişkenin o anki değerini kendi içine kopyalar, tıpkı bir fotoğraf gibi. Çünkü yerel değişken metot bitince yok olur; lambda ise çok sonra ve başka bir thread’de çalışabilir.
Bu yüzden kopyalanan değişken effectively finalfinal yazılmamış ama ilk atamadan sonra hiç değişmeyen yerel değişken. Lambda ve anonim sınıf yalnızca böyle değişkenleri yakalayabilir, çünkü değerin bir kopyasını alırlar.Sözlükte gör → olmalı: final yazmasan da ilk değerinden sonra hiç değişmemeli. Döngüde çözüm, her turda yeni bir değişken açmaktır: int n = i;.
Kafam karıştı, daha basit anlat
Lambda bir değişkenin fotoğrafını çeker. Değişken sonra değişirse fotoğraf eskir, bu yüzden Java yalnızca hiç değişmeyen değişkenlerin fotoğrafını çekmene izin verir.
Java, lambdanın yakaladığı yerel değişkenin neden effectively final olmasını ister?
Satır satır: int[] hilesi
Dizinin kendisi hiç değişmediği için derleyici memnundur. Ama lambda dizinin içindekini değil, diziye giden yolu kopyaladı; sonunda üç görev de aynı kutuya bakıyor.
Üç görev, tek dizi
int[] box = {0};for (int i = 0; i < 3; i++) { box[0] = i; tasks.add(() -> System.out.print(box[0]));}tasks.forEach(Runnable::run);Debug
main Bir dizi oluşur. box referansı bir daha değişmeyecek; bu yüzden effectively final.
- box
- = → [0]
Sol/sağ ok tuşlarıyla da gezebilirsin.
Program ne yazdırır?
Kendin gör
Lambda neyi yakalar — değeri mi, referansı mı?
Tohum 695041List<Runnable> tasks = new ArrayList<>();int[] box = {0}; // referans sabit, içerik değilfor (int i = 0; i < 3; i++) { box[0] = i; tasks.add(() -> System.out.print(box[0]));}tasks.forEach(Runnable::run);Konsol
…
Şu an ne oldu?
Üç görev oluşturulacak; her biri "kendi" sayısını yazmalı
Beklenen çıktı 0 1 2. Lambda yakaladığı şeyi kopyalıyor mu, yoksa paylaşıyor mu?
Görevler0/3
Derleyicinin reddettiği yakalamayı bulaçık
İpucu
Döngü boyunca değişen bir değişken.
Derlenen ama üç kez aynı sayıyı yazan kodu çalıştıraçık
İpucu
Derleyiciyi susturan hile ve geç çalışan görevler.
Görevler döngüden sonra çalışsın ve 0 1 2 yazsınaçık
İpucu
Her turda yeni bir değişken.
Olay günlüğü (0)
Henüz olay yok. Oynat veya adımla.
- Varsayılanla oynat. Dizi hilesi, görevler döngüden sonra:
2 2 2. - Zamanlamayı “oluşturulur oluşturulmaz” yap. Doğru çıktı; ama yalnızca zamanlama şansı.
- “Döngü sayacı i” seç. Kod hiç derlenmez.
- “Turda kopya” ve
for (int x : liste)dene. İkisi de her zaman0 1 2.
Kafam karıştı, daha basit anlat
Diziyle kandırmak işe yarar gibi görünür, ama üç görev de aynı kutuya bakar. Her turda yeni bir değişken aç: her görev kendi fotoğrafını alsın.
Simülatörde int[] box hilesi, görevler "oluşturulur oluşturulmaz" çalışınca 0 1 2 yazdı. Bu kod neden yine de güvenilmez?
Tuzaklar
Paralel stream’de dizi ya da sayaç. total[0] += x birden çok thread’den yazılınca güncellemeler kaybolur. Toplamı sum() ya da reduce ile al.
Lambdada this· istersen atla
Anonim sınıfta this anonim nesnenin kendisidir; lambdada ise lambdayı yazan nesnedir. Bu yüzden uzun yaşayan bir dinleyiciye verilen lambda, dıştaki nesneyi de bellekte tutar.
name::length ne zaman patlar?· istersen atla
name null ise name::length daha oluşturulurken patlar. () -> name.length() ise ancak çağrıldığında patlar.
Checked exception fırlatan lambda. Hazır arayüzler checked exception’a izin vermez; Files.readString çağıran bir lambda bu yüzden derlenmez. Hatayı lambdanın içinde yakala ya da kendi arayüzünü tanımla.
Bu metot bazen yanlış toplam döndürüyor. Hatalı satır hangisi?
Bir ekran sınıfı, uygulama boyunca yaşayan bir event bus'a `bus.subscribe(e -> refresh())` ile kaydoluyor. Ekran kapatıldığında neden bellekten silinmiyor?
Aşağıdaki örnek bir bankanın yurt dışı transfer ücretinden: ücret kuralları zinciri lambda’larla kuruluyor. Kendi fonksiyonel arayüzün, bileşim, method reference’lar ve capture kuralının neyi engellediği bir arada.
Derinleş · Yurt dışı transfer ücreti kuralları: fonksiyonel arayüzlerle 5 dosya · ~79 satır · ilk okumada atlayabilirsin
Kendini sına
Lambda, kullandığı yerel değişkenin o anki değerini yakalar.
Program ne yazdırır?
Aklında kalacak üç şey
- 1 Lambda, tek soyut metodu olan bir arayüzü kısaca yazmaktır. Function, Supplier, Consumer ve Predicate çoğu ihtiyacı karşılar.
- 2 Lambda yerel değişkenin o anki değerini kopyalar. Değişken sonradan değişebiliyorsa kod derlenmez, çünkü kopya ile asıl ayrışırdı.
- 3 int[] ya da AtomicInteger ile derleyiciyi susturmak lambdayı paylaşılan, değişen bir duruma bağlar. Sonuç, lambdanın ne zaman çalıştığına bağlı hale gelir.
3 kart sonraki derste seni bekliyor
Bu dersin üstüne kurulanlar
Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.
- Core Java & EşzamanlılıkOptional'ı Doğru Kullanmak — Yokluğu Tipe YazmakBir metot bazen eli boş döner. Bunu null yerine tipin kendisine nasıl yazarsın?Derse git
- Core Java & EşzamanlılıkThreadLocal ve ScopedValue — Havuzdaki Thread Neyi Hatırlıyor?Havuzdaki bir thread, bir önceki kullanıcının bilgisini nasıl hatırlayabilir?Derse git