Etkili nihai ve nihai - Farklı davranış


104

Şimdiye kadar ben düşündüm etkili bir nihai ve kesin olan az çok denk ve JLS fiili davranışlarda özdeş değilse onları benzer alacağını belirtti. Sonra bu uydurma senaryoyu buldum:

final int a = 97;
System.out.println(true ? a : 'c'); // outputs a

// versus

int a = 97;
System.out.println(true ? a : 'c'); // outputs 97

Görünüşe göre, JLS buradaki ikisi arasında önemli bir fark yaratıyor ve neden olduğundan emin değilim.

Gibi diğer konuları okudum

ama bu kadar detaya girmiyorlar. Sonuçta, daha geniş bir düzeyde, hemen hemen eşdeğer görünüyorlar. Ancak daha derine inmek, görünüşe göre farklılar.

Bu davranışa neden olan şey, bunu açıklayan bazı JLS tanımları sağlayabilir mi?


Düzenleme: Başka bir ilgili senaryo buldum:

final String a = "a";
System.out.println(a + "b" == "ab"); // outputs true

// versus

String a = "a";
System.out.println(a + "b" == "ab"); // outputs false

Yani dizge interneti burada da farklı davranıyor (Bu pasajı gerçek kodda kullanmak istemiyorum, sadece farklı davranışı merak ediyorum).


2
Çok ilginç soru! Java'nın her iki durumda da aynı şekilde davranmasını beklerdim, ancak şimdi aydınlandım. Kendime bunun her zaman davranış olup olmadığını veya önceki sürümlerde farklılık gösterip göstermediğini soruyorum
Lino

8
Aşağıda büyük bir cevap son teklifi için ifadeler @Lino aynı dönebildiyse olan Java 6 : "işlenen bir tipi ise T nerede T ise byte, shortya charve diğer işlenen sabit bir ifadesidir tip intdeğeri türü sunulabilen T , koşullu ekspresyonu daha sonra tip T ". --- Hatta Berkeley'de bir Java 1.0 belgesi buldum. Aynı metin . --- Evet, her zaman böyleydi.
Andreas

1
Bir şeyleri "bulma" şekliniz ilginç: P
Rica ederim

Yanıtlar:


65

Her şeyden önce, sadece yerel değişkenlerden bahsediyoruz . Etkili olarak nihai alanlar için geçerli değildir. finalAlanlar için anlambilim çok farklı olduğundan ve yoğun derleyici optimizasyonlarına ve bellek modeli vaatlerine tabi olduğundan , bu önemlidir, son alanların anlambilimiyle ilgili 17.5.1 $ 'a bakın .

Yüzey seviyesinde finalve effectively finalyerel değişkenler için gerçekten aynıdır. Bununla birlikte, JLS, bu gibi özel durumlarda aslında geniş bir etki yelpazesine sahip olan ikisi arasında net bir ayrım yapar.


Öncül

Gönderen JLS§4.12.4 hakkında finaldeğişkenler:

Bir sürekli değişken bir bir finaldeğişken basit tür ve tip dize bir başlatılır sabit ekspresyon ( §15.29 ). Bir değişkenin sabit bir değişken olup olmaması , sınıf başlatma ( §12.4.1 ), ikili uyumluluk ( §13.1 ), erişilebilirlik ( §14.22 ) ve kesin atama ( §16.1.1 ) ile ilgili sonuçlara sahip olabilir .

Yana intilkel, değişken abir olan sabit bir değişken .

Ayrıca, aynı bölümden şu konu hakkında effectively final:

Nihai olarak beyan edilmeyen bazı değişkenler bunun yerine etkili bir şekilde nihai kabul edilir:

Bu ifadeli şeklinden itibaren Yani, diğer örnekte, açıktır aedilir değil , olduğu gibi, sabit bir değişken olarak kabul nihai değil , ama sadece etkili bir şekilde nihai.


Davranış

Artık ayrımı bildiğimize göre, neler olup bittiğine ve çıktının neden farklı olduğuna bakalım.

? :Burada koşullu operatörü kullanıyorsunuz , bu yüzden tanımını kontrol etmeliyiz. Gönderen JLS§15.25 :

İkinci ve üçüncü işlenen ifadelerine göre sınıflandırılan üç tür koşullu ifade vardır: boole koşullu ifadeler , sayısal koşullu ifadeler ve referans koşullu ifadeler .

Bu durumda, bir bahsediyoruz sayısal koşullu ifadelerde gelen, JLS§15.25.2 :

Sayısal bir koşullu ifadenin türü şu şekilde belirlenir:

Ve bu, iki vakanın farklı şekilde sınıflandırıldığı kısımdır.

etkili bir şekilde son

effectively finalBu kuralla eşleşen sürüm :

Aksi takdirde, ikinci ve üçüncü işlenenlere genel sayısal yükseltme ( §5.6 ) uygulanır ve koşullu ifadenin türü, ikinci ve üçüncü işlenenlerin yükseltilmiş türüdür.

Yapacağınla aynı davranış 5 + 'd', yani int + charsonuçlanan int. Bkz. JLS§5.6

Sayısal yükseltme, sayısal bir bağlamdaki tüm ifadelerin yükseltilen türünü belirler. Yükseltilmiş tip, her bir ifade yükseltilmiş türe dönüştürülebilecek şekilde seçilir ve bir aritmetik işlem durumunda işlem, yükseltilmiş tipin değerleri için tanımlanır. Sayısal bağlamdaki ifadelerin sırası, sayısal yükseltme için önemli değildir. Kurallar aşağıdaki gibidir:

[...]

Daha sonra, genişleyen ilkel dönüştürme ( §5.1.2 ) ve daraltma ilkel dönüştürme ( §5.1.3 ), aşağıdaki kurallara göre bazı ifadelere uygulanır:

Sayısal seçim bağlamında, aşağıdaki kurallar geçerlidir:

Herhangi bir ifade tipi ise intve bir sabit bir ifade değildir ( §15.29 ), daha sonra terfi türüdür int, ve tip olmayan diğer ifadeler inttabi ilkel dönüşüm genişletme için int.

Yani her şey zaten olduğu intgibi yükseltilir . Bu çıktıyı açıklıyor .aint97

final

finalDeğişkeni içeren sürüm bu kuralla eşleşir:

İşlenen bir tip ise, Tburada Tolduğu byte, shortya da char, ve diğer işlenen bir sabit ifadesi ( §15.29 tipte) intdeğer türü sunulabilen T, koşullu ekspresyonu türüdür T.

Son değişken atiptedir intve sabit bir ifadedir (çünkü öyle final). Sonuç olarak gösterilebilir char, dolayısıyla sonuç türdendir char. Bu çıktıyı tamamlıyor a.


Dize örneği

Dize eşitliği örneği, aynı çekirdek farkına dayanmaktadır, finaldeğişkenler sabit ifade / değişken olarak değerlendirilir ve effectively finaldeğildir.

Java'da dize interneti sabit ifadelere dayalıdır, dolayısıyla

"a" + "b" + "c" == "abc"

olduğu true(gerçek kodda bu yapının kullanılmasına daha dont) de.

Bkz. JLS§3.10.5 :

Dahası, bir dize değişmezi her zaman aynı String sınıfına başvurur. Bunun nedeni, dize değişmezlerinin - veya daha genel olarak , sabit ifadelerin ( §15.29 ) değerleri olan dizelerin - yöntemi ( §12.5 ) kullanarak benzersiz örnekleri paylaşmak için "dahil edilmesidir " .String.intern

Öncelikle değişmezlerden bahsedildiği için gözden kaçırılması kolaydır, ancak aslında sabit ifadeler için de geçerlidir.


8
Sorun, ... ? a : 'c'ister değişken ister sabit olsun a, aynı şekilde davranmayı beklemenizdir . İfadede görünüşte yanlış bir şey yok. --- Buna karşılık, bir olan kötü ifade dizeleri ihtiyaçları kullanılarak karşılaştırılacak çünkü ( Java dizeleri karşılaştırmak nasıl? ). Ne zaman "yanlışlıkla" çalıştığını gerçeği bir olan sabit , dize hazır ait staj sadece bir cilvesi olduğunu. a + "b" == "ab"equals()a
Andreas

5
@Andreas Evet, ancak dizge stajının Java'nın açıkça tanımlanmış bir özelliği olduğunu unutmayın . Yarın veya farklı bir JVM'de değişebilecek bir tesadüf değildir. "a" + "b" + "c" == "abc"olmalı truegeçerli herhangi bir Java uygulaması içinde.
Zabuzard

10
Doğru, bu iyi tanımlanmış a + "b" == "ab"bir tuhaflık , ancak yine de yanlış bir ifadedir . Eğer bile biliyorum yani abir olan sabit , çok hataya eğilimli deme etmektir equals(). Ya da belki kırılgan daha iyi bir kelimedir, yani gelecekte kod bakımı yapıldığında parçalanma olasılığı çok yüksektir.
Andreas

2
Etkili son değişkenlerin birincil alanında, yani lambda ifadelerinde kullanımlarında bile, farkın çalışma zamanı davranışını değiştirebileceğini, yani bir yakalama ve yakalamayan lambda ifadesi arasındaki farkı yaratabileceğini unutmayın; ikincisi tekil olarak değerlendirilir. ama ilki yeni bir nesne üretiyor. Başka bir deyişle, olduğu (olmadığı) (final) String str = "a"; Stream.of(null, null). <Runnable>map( x -> () -> System.out.println(str)) .reduce((a,b) -> () -> System.out.println(a == b)) .ifPresent(Runnable::run);zaman sonucunu değiştirir . strfinal
Holger

7

Diğer bir husus, değişkenin yöntemin gövdesinde son olarak bildirilmesi durumunda, parametre olarak geçirilen son değişkenden farklı bir davranışa sahip olmasıdır.

public void testFinalParameters(final String a, final String b) {
  System.out.println(a + b == "ab");
}

...
testFinalParameters("a", "b"); // Prints false

süre

public void testFinalVariable() {
   final String a = "a";
   final String b = "b";
   System.out.println(a + b == "ab");  // Prints true
}

...
testFinalVariable();

derleyici kullanarak bilir çünkü o olur değişkeni her zaman olacaktır , böylece değerini ve sorunsuz değiştirilebilir. Farklı bir şekilde, tanımlanmamışsa veya tanımlanmışsa ancak değeri çalışma zamanında atanmışsa (yukarıdaki örnekte finalin parametre olduğu gibi) derleyici kullanımdan önce hiçbir şey bilmiyordur. Dolayısıyla, birleştirme çalışma zamanında gerçekleşir ve intern havuzu kullanılmadan yeni bir dize oluşturulur.final String a = "a"a"a"a"a"afinalfinala


Temelde davranış şudur: derleyici bir değişkenin sabit olduğunu bilirse, onu sabiti kullanmakla aynı şekilde kullanabilir.

Değişken nihai olarak tanımlanmamışsa (veya nihai ise ancak değeri çalışma zamanında tanımlanmışsa), derleyicinin, değeri bir sabite eşitse ve değeri hiçbir zaman değiştirilmediyse de, onu bir sabit olarak işlemesi için bir neden yoktur.


4
Bunda

2
Sorunun başka bir yönü.
Davide Lorenzo MARINO

5
finelAnahtar kelime bir parametre uygulanan, daha farklı sözcüklerle vardır finalvs ..., yerel bir değişkene uygulanan
DBL

6
Burada parametreyi kullanmak gereksiz gizlemedir. final String a; a = "a";
Yapıp
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.