Sorunun Tarihi:
Java 8'den önce, iç sınıflarda veya anonim sınıflarda dış kapsamda tanımlanan değişkenlerin kullanılabilmesi için bu değişkenlerin mutlaka final olarak tanımlanması gerekiyordu. Java 8 ile bu gereklilik rahatlatıldı: artık bir değişkenin final olarak tanımlanmaması mümkündür, eğer gerçekte değiştirilmezse (effectively final).
Sorun:
Lambda ifadeleri ve iç sınıflar, dış bloktaki değişkenler üzerinde kapsayıcılar kullanır. Ancak, eğer bu değişkenlerin değerleri değiştirilirse, karmaşa ve yanlış davranış ortaya çıkar — hangi değerin kullanılacağını anlamak mümkün olmaz.
Çözüm:
Derleyici, iç sınıflarda veya lambda ifadelerinde bir değişkenin kullanılmasına yalnızca o değişken effectively final olduğunda izin verir — yani başlatıldıktan sonra hiç değiştirilmemiş olmalıdır, hatta açıkça final olarak tanımlanmasa bile.
Kod örneği:
public void demo() { int x = 10; Runnable r = () -> System.out.println(x); // x — effectively final r.run(); }
Eğer x'i değiştirmeye çalışırsanız:
public void demo() { int x = 10; x = 20; // şimdi x etkili olarak final değil Runnable r = () -> System.out.println(x); // Derleme hatası }
Anahtar özellikler:
Eğer referans effectively final ise, değiştirilebilir nesneler kullanılabilir mi?
Evet, eğer referans değişmiyorsa, fakat bu referans üzerinden nesne değişiyorsa — bu mümkündür. Örneğin,
List<String> list = new ArrayList<>(); list.add("A"); Runnable r = () -> System.out.println(list.get(0)); // TAMAM list = new ArrayList<>(); // Eğer böyle olursa, derleme hatası olacaktır
Bir değişkeni final olarak tanımlayabilir miyim, sonra da nesnenin içeriğini değiştirebilir miyim?
Evet. final, referansa aittir, nesnenin içeriğine değil. Referans üzerinden nesnenin durumunu değiştirmek mümkündür. Örneğin,
final List<Integer> nums = new ArrayList<>(); nums.add(5); // TAMAM nums = new ArrayList<>(); // Hata
Method parametrelerini lambda ifadelerinde kullanabilir miyim?
Evet, eğer bunlar da effectively final ise — yani iç methodda başlatıldıktan sonra değişmiyorlarsa.
Methodda, lambda oluşturulduktan sonra yanlışlıkla yeniden yazılan değişkenler kullanılır, bu nedenle program derlenmez ve nedenini bulmak için zaman harcanır.
Artılar:
Eksiler:
Geliştirici, referansı tutan bir AtomicInteger (veya başka bir holder nesnesi) aracılığıyla artırılabilir bir değeri kullanır, bu da lambda ifadelerinin düzgün çalışmasını sağlar, gerekirse sayacı lambda ifadesi içinde değiştirmeye izin verir.
Artılar:
Eksiler: