三項演算子では許可されているが、ifステートメントでは許可されていないintとしてnullを返す


186

次のスニペットの簡単なJavaコードを見てみましょう。

public class Main {

    private int temp() {
        return true ? null : 0;
        // No compiler error - the compiler allows a return value of null
        // in a method signature that returns an int.
    }

    private int same() {
        if (true) {
            return null;
            // The same is not possible with if,
            // and causes a compile-time error - incompatible types.
        } else {
            return 0;
        }
    }

    public static void main(String[] args) {
        Main m = new Main();
        System.out.println(m.temp());
        System.out.println(m.same());
    }
}

この最も単純なJavaコードでtemp()は、関数の戻り値の型がであっても、メソッドはコンパイラエラーを発行せずint、値をnull(ステートメントを通じてreturn true ? null : 0;)返します。コンパイルすると、明らかにランタイム例外が発生しNullPointerExceptionます。

ただし、3項演算子をifsame()メソッドのように)ステートメントで表すと、コンパイル時エラーが発生し、同じことが間違っているように見えます。どうして?


6
また、int foo = (true ? null : 0)およびnew Integer(null)コンパイル罰金、オートボクシングの明示的な形である第2の両方。
Izkata

2
@Izkataここでの問題は、コンパイラーがオートボックス化しようとしている理由を理解するためのnullものIntegerです...それは、私にとって「推測」または「物事を機能させる」ように見えます...
Marsellus Wallace

1
...フム、私はそこに答えがあると思った、整数コンストラクター(私が見つけたドキュメントはオートボクシングに使用されていると言っている)は、引数として文字列(nullでもかまいません)を取ることが許可されているからです。ただし、コンストラクターはparseInt()メソッドと同じように動作し、nullが渡されるとNumberFormatExceptionがスローされるとも述べています...
Izkata

3
@Izkata-Integerの文字列引数c'torはオートボクシング操作ではありません。文字列を整数にオートボックス化することはできません。(関数Integer foo() { return "1"; }はコンパイルされません。)
Ted Hopp

5
クール、3項演算子について何か新しいことを学びました!
oksayt

回答:


118

コンパイラーはnullへのnull参照として解釈し、IntegerJava言語仕様、15.25で説明されているように)条件演算子のオートボクシング/アンボクシングルールを適用して、次に進みます。これによりNullPointerException、実行時にat が生成されます。これを試すことで確認できます。


あなたが投稿したJava言語仕様へのリンクを考えると、上記の質問の場合、どの時点で実行されると思いますか?最後のもの(私はまだ理解しようとしているのでcapture conversionlub(T1,T2))?? また、ヌル値にボクシングを適用することは本当に可能ですか?これは「推測」のようなものではないでしょうか?
Marsellus Wallace

´@ Gevorg nullポインターはすべての可能なオブジェクトへの有効なポインターであるため、そこでは何も問題は発生しません。コンパイラーは、nullが整数にオートボックスできる整数であると想定します。
Voo

1
@Gevorg-nowaqのコメントと彼の投稿に対する私の返答をご覧ください。彼は正しい節を選んだと思います。lub(T1,T2)T1とT2の型階層で共通する最も具体的な参照型です。(どちらも少なくともオブジェクトを共有しているため、常に最も具体的な参照タイプがあります。)
Ted Hopp

8
@Gevorg- Integerにボックス化されnullず、Integerへの参照として解釈されます(null参照ですが、それは問題ではありません)。Integerオブジェクトはnullから構築されないため、NumberFormatExceptionの理由はありません。
Ted Hopp

1
@Gevorg- ボクシング変換ルールを見て、それをnull(プリミティブ数値型ではない)に適用する場合、適用可能な句は「pが他の型の値の場合、ボクシング変換はID変換と同等です。 」だから、の変換ボクシングnullInteger収量をnull任意の呼び出すことなく、Integerコンストラクタを。
Ted Hopp

40

私は、Javaコンパイラの解釈だと思うtrue ? null : 0ようIntegerに暗黙的に変換できる式を、int多分与え、NullPointerExceptionます。

後者の場合には、式がnull特別なのであるヌル・タイプ を参照してくださいコードはので、return null型の不一致になります。


2
これはオートボクシングに関連していると思いますか?おそらく、最初のリターンはJava 5より前にコンパイルされませんでしたよね?
Michael McGowan

@Michaelは、Eclipseのコンプライアンスレベルを5より前に設定した場合に当てはまるようです。
Jonathon Faust

@Michael:これは間違いなくオートボクシングのように見えます(私はJavaにかなり慣れていないため、これ以上定義されたステートメントを作成できません-すみません)。
ヴラド

1
@Vladコンパイラはどのように解釈true ? null : 0するのIntegerでしょうか?0最初にオートボクシングによって??
Marsellus Wallace 2011

1
@Gevorg:ここを見てくださいそれ以外の場合、2番目と3番目のオペランドはそれぞれタイプS1とS2です。T1をS1にボクシング変換を適用した結果のタイプとし、T2をS2にボクシング変換を適用した結果のタイプとします。および次のテキスト。
Vlad

32

実際、そのすべてはJava言語仕様で説明されています

条件式のタイプは次のように決定されます。

  • 2番目と3番目のオペランドが同じ型(null型の場合もある)である場合、それは条件式の型です。

したがって、あなたの「null」は(true ? null : 0)int型を取得し、次に整数にオートボックス化されます。

これを確認するためにこのようなものを試してみてください(true ? null : null)。そうしないと、コンパイラエラーが発生します。


3
ただし、規則のその節は適用されません。2番目と3番目のオペランドは同じ型ではありませ
Ted Hopp

1
次に、答えは次のステートメントのようです。>それ以外の場合、2番目と3番目のオペランドはそれぞれタイプS1とS2です。T1をS1にボクシング変換を適用した結果のタイプとし、T2をS2にボクシング変換を適用した結果のタイプとします。条件式のタイプは、lub(T1、T2)(§15.12.2.7)にキャプチャ変換(§5.1.10)を適用した結果です。
nowaq 11/11 / 11、20:

それが当てはまると思います。次にint、関数から値を返すために自動アンボックス化を適用しようとします。これにより、NPEが発生します。
Ted Hopp

@nowaq私もこれを考えました。ただし、「T1をS1にボクシング変換を適用した結果のタイプnullIntegerするnew Integer(null);」と明示的にボックス化しようとすると、aが得られ、NumberFormatExceptionこれは
当てはまり

@Gevorgボクシングを行うときに例外が発生するので、ここでは結果を取得しません。コンパイラーは、コンパイラーが実行する定義に従うコードを生成する義務があります。完了する前に例外が発生するだけです。
Voo

25

ifステートメントの場合、null参照はそのように解釈されることを強制Integerする式に参加していないため、参照は参照として扱われません。したがって、エラーはより明確に型であるため、コンパイル時に簡単にキャッチできます。エラーであるます。

条件演算子については、Java言語仕様§15.25の「条件演算子? :」が、型変換の適用方法に関する規則でこれにうまく答えています。

  • 2番目と3番目のオペランドが同じ型(null型の場合もある)である場合、それは条件式の型です。

    でないため、適用されnullませんint

  • 2番目と3番目のオペランドの一方がブール型で、もう一方の型がブール型の場合、条件式の型はブール型になります。

    なぜならどちらも適用されませんnullintされbooleanたりBoolean

  • 2番目と3番目のオペランドの一方がNULL型で、もう一方の型が参照型である場合、条件式の型はその参照型になります。

    nullnull型であるため適用されintませんが、参照型ではありません。

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

    適用:null数値型に変換可能として扱われ、§5.1で定義されます。 8「ボックス化解除変換」をスローしNullPointerExceptionます。

場合は、0にautoboxedされInteger、その後、コンパイラはJava言語仕様で説明したように「三項演算子のルール」の最後のケースを実行しています。それが真実である場合、3項演算子の戻り値を参照型(整数)にするnullと参照型を持つ同じルールのケース3にジャンプするとは信じがたいです。 。
Marsellusウォレス

@Gevorg-三項演算子が返されていると信じがたいのはなぜIntegerですか?それがまさに起こっていることです。NPEはint、関数からを返すために式の値を開梱することによって生成されています。関数をanを返すように変更すると、問題なくInteger戻りnullます。
Ted Hopp

2
@TedHopp:Gevorgは私の回答の以前の改訂に応答していましたが、これは誤りでした。この不一致は無視してください。
ジョンパーディ

@JonPurdy「型は、それが数値型である場合、またはボックス化変換によって数値型に変換される可能性がある参照型である場合、数値型に変換可能であると言われています」と私はnullこのカテゴリに該当しないと思います。また、「それ以外の場合、バイナリ数値プロモーション(§5.6.2)が適用されます...バイナリ数値プロモーションはアンボックス化変換(§5.1.8)...を実行することに注意してください」ステップに戻り、戻り値の型を決定します。ただし、ボックス化解除変換ではNPEが生成され、これは実行時にのみ発生し、三項演算子のタイプを判別しようとするときは発生しません。私はまだ混乱しています...
Marsellusウォレスが

@Gevorg:開封は実行時に行われます。nullそれはタイプを持っていたかのように扱われているintが、実際に相当しthrow new NullPointerException()、すべてのだという。
ジョンパーディ

11

最初に覚えておかなければならないのは、Javaの三項演算子には「タイプ」があり、これが2番目または3番目のパラメーターの実際のタイプ/実際のタイプに関係なく、コンパイラーが判別して考慮するものであることです。いくつかの要因に応じて、3項演算子のタイプは、Java言語仕様15.26に示されているように、さまざまな方法で決定されます。

上記の質問では、最後のケースを考慮する必要があります。

それ以外の場合、2番目と3番目のオペランドは、それぞれタイプS1S2です。ましょうT1がにボクシング変換を適用することから生じることタイプであるS1、およびlet T2がタイプされることにボクシング変換を適用した結果S2。条件式のタイプは、lub(T1、T2)(§15.12.2.7)にキャプチャ変換(§5.1.10)を適用した結果です。

キャプチャ変換の適用(5.1.10)を見ると、これは最も複雑なケースであり、何よりもlub(T1、T2)です。

平易な英語で、極端に単純化した後、2番目と3番目のパラメーターの「最小共通スーパークラス」(そう、LCMと考えてください)を計算するプロセスを説明できます。これにより、3項演算子の「タイプ」が得られます。繰り返しますが、今言ったのは極端な単純化です(複数の共通インターフェースを実装するクラスを考えてください)。

たとえば、次のような場合:

long millis = System.currentTimeMillis();
return(true ? new java.sql.Timestamp(millis) : new java.sql.Time(millis));

条件式の結果の型はjava.util.DateTimestamp/の「最小共通スーパークラス」であるためです。Timeペアのです。

nullオートボックス化できるので、「最小共通スーパークラス」がIntegerクラスであり、これが上記の条件式(3項演算子)の戻り型になります。戻り値は、型のnullポインタになりますIntegerなり、それが三項演算子によって返されます。

実行時に、Java仮想マシンが開梱すると、Integera NullPointerExceptionがスローされます。JVMの試みが機能を起動するので、これが起こるnull.intValue()場合、nullオートボクシングの結果です。

私の意見では(そして、私の意見はJava言語仕様に含まれていないため、多くの人はとにかくそれが間違っていることに気付くでしょう)、コンパイラーはあなたの質問の式を評価するのに不十分です。あなたが書いtrue ? param1 : param2たことを考えると、コンパイラーは最初のパラメーターnullが返されることをすぐに判断する必要があり、コンパイラー・エラーを生成するはずです。これは、あなたが書いwhile(true){} etc...て、コンパイラがループの下のコードについて文句を言ってでフラグを立てるときと多少似ていますUnreachable Statements

2番目のケースはかなり単純で、この回答はすでに長すぎます...;)

補正:

別の分析の結果、null値をボックス化/オートボックス化できるというのは間違っていたと思います。クラスIntegerについて言えば、明示的なボクシングはnew Integer(...)コンストラクターまたはおそらくInteger.valueOf(int i);(このバージョンをどこかに見つけた)を呼び出すことにあります。前者はNumberFormatException(そしてこれは起こりません)をスローしますが、後者はintできないので意味がありませんnull...


1
nullOPの元のコードでは、箱詰めされていません。動作方法は次のとおりです。コンパイラは、nullが整数への参照であると想定します。3項式タイプのルールを使用して、式全体が整数式であると判断します。次に、1(条件がに評価される場合)をオートボックス化するコードを生成しますfalse。実行中、条件はに評価されるtrueため、式はに評価されnullます。int関数からを返そうとすると、nullボックスが解除されます。その後、NPEがスローされます。(コンパイラーはこれのほとんどを最適化する可能性があります。)
Ted Hopp、

4

実際、最初のケースでは式を評価できますが、コンパイラーはそれをとして評価する必要があることを知っているためInteger、2番目のケースでは戻り値(null)のタイプを判別できないため、コンパイルできません。にキャストするとInteger、コードがコンパイルされます。


2
private int temp() {

    if (true) {
        Integer x = null;
        return x;// since that is fine because of unboxing then the returned value could be null
        //in other words I can say x could be null or new Integer(intValue) or a intValue
    }

    return (true ? null : 0);  //this will be prefectly legal null would be refrence to Integer. The concept is one the returned
    //value can be Integer 
    // then null is accepted to be a variable (-refrence variable-) of Integer
}

0

これはどう:

public class ConditionalExpressionType {

    public static void main(String[] args) {

        String s = "";
        s += (true ? 1 : "") instanceof Integer;
        System.out.println(s);

        String t = "";
        t += (!true ? 1 : "") instanceof String;
        System.out.println(t);

    }

}

出力はtrue、trueです。

Eclipseは、条件式の1をオートボックス化として色分けします。

私の推測では、コンパイラは式の戻り値の型をObjectと見なしています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.