36

条件演算子について少し戸惑っています。次の 2 行を考えてみましょう。

Float f1 = false? 1.0f: null;
Float f2 = false? 1.0f: false? 1.0f: null;

f1 が null になり、2 番目のステートメントが NullPointerException をスローするのはなぜですか?

Langspec-3.0 パラ 15.25 sais:

それ以外の場合、2 番目と 3 番目のオペランドの型はそれぞれ S1 と S2 です。T1 を S1 にボックス化変換を適用した結果の型とし、T2 を S2 にボックス化変換を適用した結果の型とする。条件式の型は、キャプチャ変換 (§5.1.10) を lub(T1, T2) (§15.12.2.7) に適用した結果です。

したがって、false?1.0f:nullT1 は Float で、T2 は null 型です。しかし、の結果はlub(T1,T2)何ですか?このパラ 15.12.2.7 はちょっと多すぎます...

ところで、私は Windows で 1.6.0_18 を使用しています。

PS: Float f2 = false? (Float) 1.0f: false? (Float) 1.0f: null;NPE をスローしないことはわかっています。

4

5 に答える 5

26

違いは、コンパイル時の式の静的型付けです。

概要

E1: `(false ? 1.0f : null)`
    - arg 2 '1.0f'           : type float,
    - arg 3 'null'           : type null 
    - therefore operator ?:  : type Float (see explanation below)
    - therefore autobox arg2
    - therefore autobox arg3

E2: `(false ? 1.0f : (false ? 1.0f : null))`
    - arg 2 '1.0f'                    : type float
    - arg 3 '(false ? 1.0f : null)'   : type Float (this expr is same as E1)
    - therefore, outer operator ?:    : type float (see explanation below)
    - therefore un-autobox arg3

詳細な説明:

これは、仕様を読み、得られた結果から逆方向に作業することから得た私の理解です。つまり、f2内部条件の第 3 オペランドの型は null 型であり、f2外部条件の第 3 オペランドの型は Float と見なされます。

注:タイプの決定とボックス化/ボックス化解除コードの挿入はコンパイル時に行われることに注意してください。ボックス化/ボックス化解除コードの実際の実行は、実行時に行われます。

Float f1 = (false ? 1.0f : null);
Float f2 = (false ? 1.0f : (false ? 1.0f : null));

f1 条件と f2 内部条件: (false ? 1.0f : null)

f1 条件と f2 内部条件は同一です: (false ? 1.0f : null)。f1 条件と f2 内部条件のオペランドの型は次のとおりです。

type of second operand = float
type of third operand = null type (§4.1)

§15.25のルールのほとんどは無視され、この最終評価が実際に適用されます。

それ以外の場合、2 番目と 3 番目のオペランドの型はそれぞれ S1 と S2 です。T1 を S1 にボックス化変換を適用した結果の型とし、T2 を S2 にボックス化変換を適用した結果の型とする。条件式の型は、キャプチャ変換 ( §5.1.10 ) を lub(T1, T2) ( §15.12.2.7 ) に適用した結果です。

S1 = float
S2 = null type
T1 = Float
T2 = null type
type of the f1 and f2 inner conditional expressions = Float

f1 は Float 参照変数への代入であるため、式の結果 (null) は正常に代入されます。

f2 外部条件の場合: (false ? 1.0f : [f2 内部条件])

f2 外部条件の場合、型は次のとおりです。

type of second operand = float
type of third operand = Float

nullリテラルを直接参照する f1/f2 内部条件と比較したオペランドの型の違いに注意してください( §4.1 )。数値変換可能な型が 2 つあるというこの違いにより、§15.12.2.7の次の規則が適用されます。

  • それ以外の場合、2 番目と 3 番目のオペランドが数値型に変換可能な型 ( §5.1.8 ) を持っている場合、いくつかのケースがあります: ...

    • それ以外の場合、バイナリ数値昇格 ( §5.6.2 ) がオペランドの型に適用され、条件式の型は 2 番目と 3 番目のオペランドの昇格された型になります。バイナリ数値プロモーションでは、ボックス化解除の変換( §5.1.8 ) と値セットの変換 ( §5.1.13 ) が実行されることに注意してください。

f2 内部条件 (null) の結果に対してボックス化解除変換が実行されるため、NullPointerException が発生します。

于 2010-04-11T01:03:37.037 に答える
4

コードを書き直すと、説明がより明確になると思います。

    float f = 1.0f;

    Float null_Float  = false?        f  : null;       // float + null  -> OK
    Float null_Float2 = false? (Float)f  : null_Float; // Float + Float -> OK
    Float npe         = false?        f  : null_Float; // float + Float -> NPE

したがって、NPE は次のようなことをしようとするときです。

Float npe = false? 1.0f : (Float)null;
于 2010-04-11T01:26:31.077 に答える
2

以下は、null をプリミティブに割り当てようとすると、NPE をスローします。

    float f1 = false ? 1.0f: null;

私が信じているのは、2番目のステートメントでNPEを引き起こしているものです。最初の 3 項は true の float を返すため、false も float に変換しようとします。

必要な結果が Float であるため、最初のステートメントは null に変換されません。

これは、たとえば、プリミティブに変換する必要がなくなったため、NPE をスローしません。

    Float f = false? new Float(1.0f): true ? null : 1.0f;
于 2010-04-11T00:28:27.817 に答える
2

なるか、ならないか、それが問題だ。:)

編集:実際、よく見ると、このケースは実際にはHamlet (三項演算子とラップされた整数型) とElvis (auto-unboxing null) パズルの組み合わせのようです。いずれにせよ、私はビデオを見ることをお勧めします。それは非常に教育的で楽しいものです.

于 2010-04-11T01:26:55.380 に答える
1

JVM が 2 番目の null をFloatではなくfloatにボックス化解除しようとするように見えるため、NullPointerException が発生します。自分で一度打ってみよう。私の見解では、最初のifの真の部分がFloat ではなく float として評価されるため、2 番目のifがそれを行うということです。

よく考えてみると、これは Java が何かおかしなことをしていると伝える方法だと思います。三項ifを入れ子にしないだけで大丈夫です:-)

于 2010-04-11T00:22:52.130 に答える