Java 1.1, tanımlayıcıda hemen atanmayı zorlamadan esnek değişmez kalıpları desteklemek için boş final değişkenleri - başlangıç ataması olmayan final olarak tanımlanan alanlar - getirdi. Temel sorun, bu alanların kullanım öncesi her olası yürütme yolunda tam olarak bir kez atanmasını sağlamaktır; bu, try-catch blokları, dallanma mantığı ve başlangıç atamasını atlayabilecek erken dönüşler gibi zorluklar tarafından karmaşık hale gelir. Bunun çözümü olarak, derleyici kontrol akış grafiği (CFG) üzerinde Kesin Atama (DA) analizi gerçekleştirir ve her program noktasında kesin bir şekilde atanmış değişkenler kümesini takip eder; final değişkenler için, alanın iki kez yazılmadığını garanti etmek amacıyla ayrıca Kesin Atanmama (DU) analizi gerçekleştirir. Bytecode doğrulayıcı sınıf yükleme zamanında bu kısıtlamaları StackMapTable özelliği ve tür kontrolü aracılığıyla zorunlu kılar ve hiçbir talimatın kesin bir şekilde atanmış olmayan bir değişkeni okumadığından emin olur.
Bir finansal hizmetler ekibi, yapıcı içinde harici bir hizmet çağrısı yoluyla üretilen bir final UUID tradeId ile birlikte bir ImmutableTrade sınıfı oluşturdu. Yapıcı, hatayı günlüğe kaydetmek ve tekrar fırlatmak için bu çağrıyı bir try-catch bloğuna sardı, ancak catch bloğunda tradeId'yi atamayı başaramadı; bu, derleyicinin Kesin Atama analizinin olağanüstü yolun final alanını başlatılmamış bıraktığını tespit etmesi nedeniyle bir derleme hatasına neden oldu.
Önerilen bir çözüm, catch bloğunda tradeId'yi null olarak başlatmaktı, ancak bu, her bir ImmutableTrade'in geçerli bir tanımlayıcıya sahip olma iş kuralını ihlal etti ve potansiyel olarak aşağı akışta NullPointerException'a yol açtı ve final alanının garanti ettiği amacını boşa çıkardı. Diğer bir yaklaşım, atama durumunu takip etmek için bir boolean bayrağı kullanmayı içeriyordu, ama bu, değiştirilebilir durumu ve gereksiz karmaşıklığı ekleyerek, ekibin ulaşmak istediği değişmezlik ve iş parçacığı güvenliğini zayıflattı. Ekibin nihayetinde tercih ettiği, hizmet çağrısını harici olarak gerçekleştiren ve elde edilen UUID'yi özel bir yapıcıya ileten bir statik fabrika örneği olarak yeniden tasarladı ve böylece alanın geçerli bir değerle tam olarak bir kez atandığından emin oldu.
Bu yaklaşım, derleyicinin katı DA analizine uydu ve sahte değerlere ihtiyaç duymadan sınıfın sözleşmeli değişmezliğini korudu, ayrıca hizmet sonuçlarının ön doğrulamasını ve önbelleğe alınmasını sağladı. Ortaya çıkan kod tabanı derlemeyi ve katı stres testlerini geçti ve kesin atama kurallarına uyulmasının üretimde olası NullPointerException senaryolarını önlediğini ve ImmutableTrade nesnelerinin eşzamanlı iş parçacıkları arasında senkronizasyon aşırı yükü olmadan güvenli bir şekilde paylaşımına izin verdiğini gösterdi.
Yansıma, yapıcıdan sonra bir final alanını değiştirebilir mi ve böyle değişiklikler diğer kodlara neden görünmez kalabilir?
Yansıma, Field#setAccessible(true) ve set() kullanarak örnek final alanlarını değiştirebilir, ancak derleme zamanında sabitlerle başlatılan static final alanlar, derleyici tarafından istemci bytecode'ına literal değerler olarak iç içe geçirilir. Bu nedenle, bu tür sabitlere yapılan yansıtıcı değişiklikler, zaten derlenmiş sınıflara görünmez; bu sınıflar sabit havuzu girişine atıfta bulunur, alan yerine. Ayrıca, JVM, gerçekten final alanları optimizasyon için değişmez olarak kabul eder, bu da değişiklik zorlamak için VarHandle'a ve private lookup veya Unsafe'ye ihtiyaç duyar ve yine de, CPU önbellekleri değişikliği, açık bellek bariyerleri olmadan gözlemlemeyebilir ve bu da ince görünürlük hatalarına yol açabilir.
Yapıcı sırasında 'this' referansının kaçmasını engellemek, final alanlar için kesin atama garantileri ile nasıl etkileşir?
DA analizi, final bir alanın yapıcı döndükten önce atandığını teyit etse bile, yapıcı sırasında başka bir iş parçacığına this'i yayımlamak (örneğin, bir dinleyici veya kayıt aracılığıyla) diğer iş parçacığının varsayılan değeri (sıfır/null) gözlemlemesine neden olacak bir yarış durumu oluşturur. Java Bellek Modeli, yapıcı tamamlama sonrasında tüm iş parçacıklarının final alanın değerini doğru bir şekilde görmesini garanti eder, ancak yapıcı sırasında böyle bir garanti vermez. Dolayısıyla, kesin atama katı bir statik derleme zamanı özelliğidir ve tek atamayı sağlarken, güvenli yayılım this'in yapıcıdan kaçmasını önlemeyi gerektirir, tüm final alanlar saklandığından önce.
Derleyici neden bir döngü içinde bir boş final alana atamayı reddeder, mantık bunun tam olarak bir kez çalıştırıldığını öne sürse bile?
Derleyici, temkinli bir statik analiz gerçekleştirir ve bir döngünün tam olarak bir kez çalıştığını veya sıfır kez döngüye girmediğini kanıtlayamaz; döngüler, DA izlemeyi karmaşıklaştıran kontrol akış grafiğinde geri kenarları tanıtır. Çünkü bir final alanın tam olarak bir kez atanmış olması gerekir, birden fazla iterasyon (birden fazla atama) veya sıfır iterasyon (atama yok) olasılığı, boş final için gerekli olan Kesin Atanmama şartını ihlal eder. Sonuç olarak, derleyici, boş final alanlara atamaların döngüler dışında veya belirsiz bir tek atama semantiği ile dallarda gerçekleşmesini zorunlu kılarak, insanların mantıklı bir şekilde doğrulayabileceği ancak CFG'nin garanti edemediği kodu reddeder.