İçeriğe geç

Lambda ve Fonksiyonel Arayüzler — Lambda Neyi Yakalar?

Orta 8 dk Çok sık karşılaşılır

Ö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ı.

Fotoğraf çekildiği anı, pencere baktığın anı gösterir.
Adım adım oku
  1. Lambda, kullandığı yerel değişkenin o anki değerinin fotoğrafını çeker: i = 1.
  2. Fotoğraf sonradan değişmeyeceği için değişkenin de değişmemesi gerekir; derleyici i = 2'ye izin vermez.
  3. Bir diziyi ya da nesneyi yakaladığında ise fotoğraf değil, pencere alır: kutunun kendisine bakar.
  4. Kutunun içi değişirse lambda da çalıştığı anda yeni hâlini görür. Üç görevin üçü de 2 yazar.
  1. Bayt: Döngüde üç görev kurdum, her biri kendi sıra numarasını yazacaktı.

  2. Sen: Derleyici i'ye itiraz etti. Ben de sayıyı bir dizi kutuya koyup susturdum.

  3. Bayt: Görevler çalıştı ve üçü de 2 yazdı?!

  4. 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üzMetotNe 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.

Hızlı kontrolBaşlangıç

`Comparator<T>` arayüzünde hem `compare` hem de `equals` soyut metot olarak bildirilmiş. Yine de neden bir functional interface sayılır?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Her method reference'ı türüne göre ayır.

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sınıflandırılmamış

Static metot

Sınıf adı + static metot; argümanlar doğrudan geçer.

    Bağlı alıcı

    Belirli bir nesnenin metodu; nesne şimdiden belli.

      Bağlı olmayan alıcı

      Sınıf adı + instance metodu; ilk argüman alıcı olur.

        Constructor

        ::new

          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.

          Hızlı kontrolOrta

          Java, lambdanın yakaladığı yerel değişkenin neden effectively final olmasını ister?

          Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

          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

          Tasks.java
          şu an çalışan satırint[] box = {0};
          2for (int i = 0; i < 3; i++) {
          3 box[0] = i;
          4 tasks.add(() -> System.out.print(box[0]));
          5}
          6tasks.forEach(Runnable::run);

          Debug

          Adım 1/4

          main Bir dizi oluşur. box referansı bir daha değişmeyecek; bu yüzden effectively final.

          box
          = → [0]
          Java 21UTF-8LF1:1

          Sol/sağ ok tuşlarıyla da gezebilirsin.

          Hızlı kontrolOrta

          Program ne yazdırır?

          Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.
          Capture.java
          1List<Supplier<String>> parts = new ArrayList<>();
          2StringBuilder sb = new StringBuilder("a");
          3String s = "x";
          4parts.add(() -> sb.toString());
          5parts.add(() -> s);
          6sb.append("b");
          7parts.forEach(p -> System.out.print(p.get() + " "));
          Java 21UTF-8LF

          Kendin gör

          Lambda neyi yakalar — değeri mi, referansı mı?

          Tohum 695041
          Tasks.java
          1List<Runnable> tasks = new ArrayList<>();
          2int[] box = {0}; // referans sabit, içerik değil
          3for (int i = 0; i < 3; i++) {
          4 box[0] = i;
          5 tasks.add(() -> System.out.print(box[0]));
          6}
          7tasks.forEach(Runnable::run);
          Java 21UTF-8LF

          Konsol

          …
          Hız
          Adım 0

          Ş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.

          1. Varsayılanla oynat. Dizi hilesi, görevler döngüden sonra: 2 2 2.
          2. Zamanlamayı “oluşturulur oluşturulmaz” yap. Doğru çıktı; ama yalnızca zamanlama şansı.
          3. “Döngü sayacı i” seç. Kod hiç derlenmez.
          4. “Turda kopya” ve for (int x : liste) dene. İkisi de her zaman 0 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.

          Hızlı kontrolOrta

          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?

          Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

          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.

          Hızlı kontrolOrta

          Bu metot bazen yanlış toplam döndürüyor. Hatalı satır hangisi?

          Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

          Hatalı satıra dokun, sonra kontrol et.

          Revenue.java
          Java 21UTF-8LF

          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?

          Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

          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
          Proje dosyaları

          src/main/java/fees/ FeeRule.java Kendi fonksiyonel arayüzün: tek soyut metot, bileşim için default metot.

          src/main/java/fees/FeeRule.java
          @FunctionalInterface
          public interface FeeRule {
          BigDecimal apply(Customer customer, BigDecimal fee); // the single abstract method
          // Composition without new classes: this rule, then the next one.
          default FeeRule andThen(FeeRule next) {
          return (customer, fee) -> next.apply(customer, this.apply(customer, fee));
          }
          static FeeRule identity() {
          return (customer, fee) -> fee;
          }
          }

          src/main/java/fees/ Rules.java Kurallar veri gibi: hazır kurallar ve parametre alan kural fabrikaları.

          src/main/java/fees/Rules.java
          public final class Rules {
          private Rules() {}
          // A constant rule: a lambda stored in a field.
          public static final FeeRule ROUND_TO_KURUS =
          (customer, fee) -> fee.setScale(2, RoundingMode.HALF_UP);
          // A rule factory: the lambda captures `percent`, which is effectively final.
          public static FeeRule percentOff(int percent) {
          BigDecimal factor = BigDecimal.valueOf(100 - percent).movePointLeft(2);
          return (customer, fee) -> fee.multiply(factor);
          }
          // Built from a predicate: rules can be conditional without an if in the engine.
          public static FeeRule when(Predicate<Customer> condition, FeeRule rule) {
          return (customer, fee) -> condition.test(customer) ? rule.apply(customer, fee) : fee;
          }
          public static boolean isLongStanding(Customer customer) { // used as a method reference
          return customer.yearsWithBank() >= 3;
          }
          }

          src/main/java/fees/ FeeEngine.java Kuralları sırayla uygulayan motor; hangi kuralın çalıştığı lambda'ya değil listeye bağlı.

          src/main/java/fees/FeeEngine.java
          public class FeeEngine {
          private final FeeRule pipeline;
          public FeeEngine(List<FeeRule> rules) {
          // reduce: fold the list into ONE composed rule, once, at construction.
          this.pipeline = rules.stream().reduce(FeeRule.identity(), FeeRule::andThen);
          }
          public BigDecimal feeFor(Customer customer, BigDecimal baseFee) {
          return pipeline.apply(customer, baseFee);
          }
          }

          src/main/java/fees/ CaptureRules.java Capture kuralı: dışarıdaki değişkeni değiştiremezsin; bunun doğru karşılığı akışın kendisi.

          src/main/java/fees/CaptureRules.java
          public class CaptureRules {
          static int totalTransfers(List<Customer> customers) {
          int total = 0;
          // customers.forEach(c -> total += c.transfersThisMonth());
          // does not compile: `total` is not effectively final. A lambda may run
          // later or on another thread, so Java forbids mutating captured locals.
          // The functional answer is not a workaround, it is the computation itself:
          return customers.stream().mapToInt(Customer::transfersThisMonth).sum();
          }
          }

          src/main/java/fees/ Main.java Çalıştıran program ve çıktılar.

          src/main/java/fees/Main.java
          public class Main {
          public static void main(String[] args) {
          var engine = new FeeEngine(List.of(
          Rules.when(Rules::isLongStanding, Rules.percentOff(10)), // method reference as Predicate
          Rules.percentOff(5), // this month's mobile campaign
          Rules.ROUND_TO_KURUS));
          BigDecimal swiftFee = new BigDecimal("200.00"); // illustrative base fee
          Customer longStanding = new Customer("C-1", 5, 4); // 5 years, 4 transfers
          Customer newcomer = new Customer("C-2", 0, 4);
          System.out.println(engine.feeFor(longStanding, swiftFee)); // 171.00 (200 × 0.90 × 0.95)
          System.out.println(engine.feeFor(newcomer, swiftFee)); // 190.00 (200 × 0.95)
          System.out.println(CaptureRules.totalTransfers(List.of(longStanding, newcomer))); // 8
          }
          }

          Kendini sına

          Şimşek turu1/5

          Lambda, kullandığı yerel değişkenin o anki değerini yakalar.

          Soru 1/2İleri

          Program ne yazdırır?

          Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.
          Lazy.java
          1String name = null;
          2Supplier<Integer> viaLambda = () -> name.length();
          3System.out.println("lambda hazır");
          4Supplier<Integer> viaRef = name::length;
          5System.out.println("referans hazır");
          Java 21UTF-8LF

          Aklında kalacak üç şey

          1. 1 Lambda, tek soyut metodu olan bir arayüzü kısaca yazmaktır. Function, Supplier, Consumer ve Predicate çoğu ihtiyacı karşılar.
          2. 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. 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.
          Sonraki kapı Müşterileri ada göre sıraladın ve Çağla, Zeynep'ten sonra çıktı. Nasıl olur? Comparable ve Comparator — Kim Önce Gelir? · 8 dk

          3 kart sonraki derste seni bekliyor

          0/5 kart bu dersten toplandı

          Bu dersin üstüne kurulanlar

          Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.