2015-10-16 11 views
8

時にはnullになる三項演算子を使用しているときに、1人の学生がnullポインタ例外を受け取ります。私は問題を理解していると思うが、それは一貫性のない型推論の結果であるようだ。あるいは、別の言い方をすれば、私はここでの意味が矛盾しているように感じ、エラーは彼のアプローチを変えることなく避けるべきです。Java Ternary演算子がIntegersをint型に不一致でキャストしようとしています

この質問は、Another question about ternary operatorsと似ていますが、異なっています。その質問では、関数の戻り値がintであるため、null Integerはintに強制されなければなりません。しかし、それは私の生徒のコードでは当てはまりません。

このコードは正常に動作:

Integer x = (5>7) ? 3 : null; 

xの値がnullです。 NPEはありません。この場合、コンパイラは三項演算子の結果がIntegerである必要があることを知ることができるので、intをintにキャストするのではなくIntegerに3をキャストします。

しかし、このコード実行:NPEで

Integer x = (5>7) ? 3 : (5 > 8) ? 4 : null; 

結果を。唯一の理由は、nullがintにキャストされるためですが、実際には必要ではなく、最初のコードと矛盾しているようです。つまり、コンパイラが最初のスニペットに対して、3項演算子の結果がIntegerであると推測できる場合、なぜ2番目のケースでそれを行うことができないのでしょうか? 2番目の3進表現の結果はIntegerでなければならず、その結果が最初の3進演算子の2番目の結果であるため、最初の3進演算子の結果もIntegerでなければなりません。

別snipetが正常に動作します:

ここ
Integer three = 3; 

Integer x = (5>7) ? three : (5 > 8) ? three+1 : null; 

、コンパイラは、両方の三項演算子の結果は整数であるので、強制的にキャストしていないヌルをint型にすることを推測することができるようです。

+0

オートボックを調べます。 –

+0

nullポインタへのアクセス:Integer型のこの式はnullですが、自動アンボッキングが必要です –

+0

これらの質問も参照してください:[Java autoboxing and ternary madness](http://stackoverflow.com/questions/25417438/java-autoboxing-and-ternary -operator-madness)と[Java 3値の自動ボクシングによるNPE](http://stackoverflow.com/questions/12763983/nullpointerexception-through-auto-boxing-behavior-of-java-ternary-operator)を参照してください。 –

答えて

10

これの鍵は、条件演算子が右結合であることです。条件式の結果の型を決定するための規則はhideously complicatedですが、それはこれに沸く:私たちがそれを見れば

  1. まず(5 > 8) ? 4 : null評価されるが、第二オペランドは、int、第三はnullですこの式の結果の型はIntegerです。 (換言すれば、オペランドの一方がnullあるため、これは基準条件式として扱われる)
  2. その後、我々は、上記のリンクテーブルに、我々は、ルックアップする必要があることを意味し、評価する(5>7) ? 3 : <previous result>を有します第2オペランドintと第3オペランドIntegerの結果の型:およびそれはintです。これは、<previous result>をアンボックスする必要があり、NPEで失敗することを意味します。

なぜ最初のケースがその後に機能しますか?そこ

我々は第二オペランドがintで、第三がnullであれば、結果の型がIntegerで見てきたように、(5>7) ? 3 : null;を持っています。しかし、それをInteger型の変数に代入するので、unboxingは必要ありません。

しかし、これが唯一のnullリテラルで発生し、次のコードはまだ数値条件式にNPE、理由はオペランドの型intInteger結果がスローされます。それを要約すると

Integer i = null; 
Integer x = (5>7) ? 3 : i; 

:ありそれに対する論理の一種ですが、人間の論理ではありません。

  1. 両方のオペランドがコンパイルタイプIntegerの場合、結果はIntegerになります。
  2. オペランドのタイプがintで、もう1つがIntegerの場合、結果はintです。
  3. オペランドの1つがnull type(有効な値がnullの唯一の値)の場合、結果はIntegerになります。ヌル以来、NPEで
+0

素敵な要約。なぜJLSのテーブルが '? int:整数 'と'? Integer:int' Integerではなくintにマップしますか? –

+0

@AndyThomas 1つしか推測できませんが、私はその考えは、とにかく 'int'変数に結果を代入し、直ちにそれを破棄するために余分な' Integer'インスタンスを作成しようとしていると思うと思います高価すぎると見られた。しかし、これは実際には推測以上のものではなく、他のより有効な理由があるかもしれません。 – biziclop

+0

<前の結果>はなぜintですが、<前の結果>と同じ形式の最初の例では答えは整数ですか? –

1
int x = (int) (5>7) ? 3 : null; 

結果がintにキャストされます。あなたのコード

Integer x = (5>7) ? 3 : (5 > 8) ? 4 : null; 

と同じ

は次のようになります。

Integer x = (Integer) ((5>7) ? (int) 3 : (5 > 8) ? 4 : null); 

あなたが再び失敗し、intにキャストされたヌル見ての通り。

Integer x = (Integer) ((5>7) ? (Integer) 3 : (5 > 8) ? 4 : null); 

今はint型にする場合はnullにキャストしようとしますが、整数ません。これを解決するために

はこれを行うことができます。

0

Java8より前では、ほとんどの場合、&dagger;では、式の型は完全に下位式の型に依存してボトムアップされます。それは文脈に依存しません。これは素敵でシンプルであり、コードは分かりやすいです。たとえば、オーバーロードの解決は、メソッドの呼び出しコンテキストとは独立して解決される引数のタイプによって異なります。

&ダガー、私の知る唯一の例外は、jls#15.12.2.8である)?int:Integerの形で条件式を考える

、スペックは、コンテキストとは関係なく、それのために固定型を定義する必要があります。 intタイプが選択されました。ほとんどのユースケースではおそらくこれが優れています。 もちろん、アンボックス化からのNPEのソースでもあります。


Java8では、コンテキスト・タイプの情報を型推論に使用できます。これは多くの場合に便利です。式の型を解決するには2つの方向があるかもしれないので、混乱も招く。幸いにも、いくつかの表現はまだ独立しています。それらの型は文脈に依存しない。

w.r.条件式では、false?0:1のような簡単なものは文脈依存であるとは思わない。彼らのタイプは自明です。一方、false?f():g()のようなより複雑な条件式についてはコンテキスト型の推論が必要です。ここで、f/g()には型推論が必要です。

このラインは、原型と参照型の間に描かれました。 op1?op2:op3では、op2op3の両方が「はっきり」のプリミティブタイプ(またはボックス版)である場合、スタンドアロンとして扱われます。 Quotingダン・スミス -

は、我々はブール値と数値条件文の既存の動作を維持しながら、参照条件文(15.25.3)の型付け規則を強化するために、ここで条件式を分類します。すべての条件を一様に扱おうとすると、過負荷の解決やボクシング/アンボクシングの動作の変更など、さまざまな望ましくない互換性のない変更が発生します。 false?4:null以来

あなたの場合

Integer x = false ? 3 : false ? 4 : null; 

"明確に" Integerで、親式は?:int:Integerの形態です(?)。これは原始的なケースであり、その動作はjava7との互換性を維持しているため、NPEです。


これは私の直感的な理解であるため、「明確に」引用しています。私は正式な仕様についてはわかりません。この例を見てみましょう

static <T> T f1(){ return null; } 
-- 

Integer x = false ? 3 : false ? f1() : null; 

コンパイル!実行時にはNPEはありません!私はこの場合の仕様に従う方法を知らない。コンパイラはおそらく次のステップを実行すると思います:

1)サブ式false?f1():nullは「はっきり」(ボックス化された)プリミティブ型ではありません。そのタイプは未知であるが、

2)したがって、親表現は、「参照条件式」として分類コンテキストに現れる。

3)ターゲット・タイプIntegerは、オペランドに適用され、その後、推定され、最終的にf1()は、我々はすぐに戻って行くことができない?int:Integerとしての条件式を再分類する、しかし)Integer

4を返すために、 。


これは妥当と思われます。しかし、型引数を明示的にf1()に指定するとどうなりますか?

Integer x = false ? 3 : false ? Test.<Integer>f1() : null; 

理論(A) - それは推測されていたであろうと同じタイプの引数だから、これは、プログラムのセマンティクスを変更しないでください。私たちは実行時にNPEを見るべきではありません。

理論(B) - 型推論はありません。サブ式のタイプは明らかにIntegerなので、これは原始的なケースとして分類されるべきであり、実行時にNPEを参照する必要があります。

私は(B)を信じています。しかし、javac(8u60)は(A)を実行します。なぜか分からない。


意味をなさない陽気なレベル

class MyList1 extends ArrayList<Integer> 
    { 
     //inherit public Integer get(int index) 
    } 

    class MyList2 extends ArrayList<Integer> 
    { 
     @Override public Integer get(int index) 
     { 
      return super.get(0); 
     } 
    } 

    MyList1 myList1 = new MyList1(); 
    MyList2 myList2 = new MyList2(); 

    Integer x1 = false ? 3 : false ? myList1.get(0) : null; // no NPE 
    Integer x2 = false ? 3 : false ? myList2.get(0) : null; // NPE !!! 

にこの観察を押します。 javacの中で何か本当にファンキーなことが起こっています。

Java autoboxing and ternary operator madnessも参照)

+0

レッスンの学習 - 常にop2とop3で明示的に同じ型を使用します。 'int'と' Integer'を混在させないでください。必要に応じて手動明示的ボクシング/アンボクシングを行います。ここで 'int'と' long'などを混ぜてはいけません。必要に応じて手動のプリミティブ型変換を行います。仕様のこの部分はあまりにも信頼性がありません。 – ZhongYu

関連する問題