Booleanlar, koşullu operatörler ve otomatik kutulama


132

Bu neden fırlatıyor NullPointerException

public static void main(String[] args) throws Exception {
    Boolean b = true ? returnsNull() : false; // NPE on this line.
    System.out.println(b);
}

public static Boolean returnsNull() {
    return null;
}

bu olmazken

public static void main(String[] args) throws Exception {
    Boolean b = true ? null : false;
    System.out.println(b); // null
}

?

Solüsyon yerine arada olduğu falseile Boolean.FALSEönlemek nullkutulamasının olmak booleanmümkün değildir --which. Ama soru bu değil. Soru neden ? JLS'de, özellikle 2. durum için bu davranışı doğrulayan herhangi bir referans var mı?


28
vay canına, otomatik kutulama java programcısı için sonsuz bir sürpriz kaynağı, değil mi? :-)
leonbloy

Benzer bir sorun yaşadım ve beni şaşırtan şey, OpenJDK VM'de başarısız olması, ancak HotSpot VM'de çalışmasıydı ... Bir kez yazın, her yerde çalıştırın!
kodu

Yanıtlar:


92

Aradaki fark, returnsNull()yöntemin açık türünün , derleme zamanında ifadelerin statik tiplemesini etkilemesidir:

E1: `true ? returnsNull() : false` - boolean (auto-unboxing 2nd operand to boolean)

E2: `true ? null : false` - Boolean (autoboxing of 3rd operand to Boolean)

Java Dil Belirtimi, bölüm 15.25 Koşullu Operatör? :

  • E1 için, 2. ve 3. işlenen türleridir Booleanve booleanbu fıkra uygular böylece sırasıyla:

    İkinci ve üçüncü işlenenlerden biri boolean türündeyse ve diğerinin türü Boolean türündeyse, koşullu ifadenin türü boole'dur.

    İfadenin türü olduğundan boolean, 2. işlenen için zorlanmalıdır boolean. Derleyici, yazması için 2. işlenene (dönüş değeri returnsNull()) otomatik kutudan çıkarma kodu ekler boolean. Bu tabii ki nullçalışma zamanında döndürülen NPE'ye neden olur .

  • E2 için, 2. ve 3. işlenenlerin türleri <special null type>( BooleanE1'deki gibi değil !) Ve booleansırasıyla ve bu nedenle belirli bir yazma cümlesi uygulanmaz ( gidin 'em'lerini okuyun! ), Bu nedenle son "aksi halde" cümlesi geçerlidir:

    Aksi takdirde, ikinci ve üçüncü işlenenler sırasıyla S1 ve S2 tiplerindedir. T1'in S1'e boks dönüşümünün uygulanmasından kaynaklanan tür olmasına izin verin ve T2, S2'ye boks dönüşümü uygulamasından kaynaklanan tür olsun. Koşullu ifadenin türü, yakalama dönüşümünün (§5.1.10) lub (T1, T2) (§15.12.2.7) 'e uygulanmasının sonucudur.

    • S1 == <special null type>(bkz. §4.1 )
    • S2 == boolean
    • T1 == kutu (S1) == <special null type>( §5.1.7'deki boks dönüşümleri listesindeki son öğeye bakın )
    • T2 == kutu (S2) == `Boole
    • lub (T1, T2) == Boolean

    Yani koşullu ifadenin türü Booleanve 3. işlenen için zorlanmalıdır Boolean. Derleyici, 3. işlenen ( false) için otomatik kutulama kodunu ekler . 2. işlenen, olduğu gibi otomatik kutudan çıkarmaya ihtiyaç duymaz E1, bu nedenle nulldöndürüldüğünde otomatik kutudan çıkan NPE olmaz .


Bu sorunun benzer bir tür analizine ihtiyacı var:

Java koşullu işleci?: Sonuç türü


4
Mantıklı ... Sanırım. §15.12.2.7 bir acıdır.
BalusC

Kolay ... ama sadece geriye dönüp bakıldığında. :-)
Bert F

@BertF Ne işlevini yapar lubiçinde lub(T1,T2)için standı?
Geek

1
@Geek - lub () - en az üst sınır - temelde ortak oldukları en yakın süper sınıf; null ("özel boş tür" türü) örtük olarak herhangi bir türe dönüştürülebildiğinden (genişletilebildiğinden), özel boş türünün lub () amaçları doğrultusunda herhangi bir türden (sınıf) bir "üst sınıf" olduğunu düşünebilirsiniz.
Bert F

25

Çizgi:

    Boolean b = true ? returnsNull() : false;

dahili olarak şuna dönüştürülür:

    Boolean b = true ? returnsNull().booleanValue() : false; 

kutudan çıkarmayı gerçekleştirmek için; böylece: null.booleanValue()bir NPE verir

Bu, otomatik kutuyu kullanırken en büyük tuzaklardan biridir. Bu davranış aslında 5.1.8 JLS'de belgelenmiştir.

Düzenleme: Kutudan çıkarmanın üçüncü operatörün boole tipinde olmasından kaynaklandığına inanıyorum, örneğin (örtük atama eklendi):

   Boolean b = (Boolean) true ? true : false; 

2
Nihai değer bir Boolean nesnesi iken, neden bu şekilde kutuyu açmaya çalışıyor?
Erick Robertson

16

Gönderen Java Dil Şartname, bölüm 15,25 :

  • İkinci ve üçüncü işlenenlerden biri boolean türündeyse ve diğerinin türü Boolean türündeyse, koşullu ifadenin türü boole'dur.

Bu nedenle, ilk örnek , ilk kurala göre Boolean.booleanValue()dönüştürmek Booleaniçin çağırmaya çalışır boolean.

İkinci durumda, birinci işlenen boş tiptedir, ikincisi referans tipinde olmadığında, otomatik kutu dönüşümü uygulanır:

  • Aksi takdirde, ikinci ve üçüncü işlenenler sırasıyla S1 ve S2 tiplerindedir. T1'in S1'e boks dönüşümünün uygulanmasından kaynaklanan tür olmasına izin verin ve T2, S2'ye boks dönüşümü uygulamasından kaynaklanan tür olsun. Koşullu ifadenin türü, yakalama dönüşümünün (§5.1.10) lub (T1, T2) (§15.12.2.7) 'e uygulanmasının sonucudur.

Bu ilk vakayı yanıtlar, ancak ikinci vakayı cevaplamaz.
BalusC

Muhtemelen değerlerden birinin olduğu durumlar için bir istisna vardır null.
Erick Robertson

@Erick: JLS bunu onaylıyor mu?
BalusC

1
@Erick: booleanReferans türü olmadığı için uygulanabilir olduğunu sanmıyorum .
axtavt

1
Ve ekleyebilir miyim ... bu nedenle, gerekirse açık çağrılarla bir üçlü değerin her iki tarafını da aynı tür yapmalısınız. Spesifikasyonları ezberlemiş olsanız ve ne olacağını bilseniz bile, bir sonraki programcı gelip kodunuzu okuyabilir. Benim mütevazi görüşüme göre, derleyicinin bu durumlarda sıradan ölümlülerin tahmin etmesi zor şeyler yapmaktansa bir hata mesajı üretmesi daha iyi olurdu. Belki davranışın gerçekten yararlı olduğu durumlar vardır, ama henüz bir tanesine ulaşmadım.
Jay

0

Bu problemi bayt kodundan görebiliriz. Ana bayt kodunun 3. satırında, 3: invokevirtual #3 // Method java/lang/Boolean.booleanValue:()Zboş değerli boks Boole değeri invokevirtual, yöntem java.lang.Boolean.booleanValue, tabii ki NPE'yi atacaktır.

    public static void main(java.lang.String[]) throws java.lang.Exception;
      descriptor: ([Ljava/lang/String;)V
      flags: ACC_PUBLIC, ACC_STATIC
      Code:
        stack=2, locals=2, args_size=1
           0: invokestatic  #2                  // Method returnsNull:()Ljava/lang/Boolean;
           3: invokevirtual #3                  // Method java/lang/Boolean.booleanValue:()Z
           6: invokestatic  #4                  // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
           9: astore_1
          10: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
          13: aload_1
          14: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
          17: return
        LineNumberTable:
          line 3: 0
          line 4: 10
          line 5: 17
      Exceptions:
        throws java.lang.Exception

    public static java.lang.Boolean returnsNull();
      descriptor: ()Ljava/lang/Boolean;
      flags: ACC_PUBLIC, ACC_STATIC
      Code:
        stack=1, locals=0, args_size=0
           0: aconst_null
           1: areturn
        LineNumberTable:
          line 8: 0
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.