<JDK8との互換性におけるJavaの三項演算子とif / else


113

最近、Spring Frameworkのソースコードを読んでいます。理解できないことがここにあります:

public Member getMember() {
    // NOTE: no ternary expression to retain JDK <8 compatibility even when using
    // the JDK 8 compiler (potentially selecting java.lang.reflect.Executable
    // as common type, with that new base class not available on older JDKs)
    if (this.method != null) {
        return this.method;
    }
    else {
        return this.constructor;
    }
}

このメソッドは、クラスのメンバーですorg.springframework.core.MethodParameter。コメントは難しいですが、コードは理解しやすいです。

注:JDK 8コンパイラを使用している場合でもJDK <8の互換性を維持するための三項式java.lang.reflect.Executableはありません(古いタイプのJDKでは利用できない新しい基本クラスで、共通タイプとして選択する可能性があります)

if...else...この文脈で三項式を使用することと構成を使用することの違いは何ですか?

回答:


103

オペランドのタイプについて考えると、問題はより明白になります。

this.method != null ? this.method : this.constructor

すなわち両方に最も特殊なタイプの一般的な両方のオペランドの最も特殊な一般的なタイプ、入力したthis.methodとしますthis.constructor

これはJava 7 java.lang.reflect.Memberではありますが、Java 8クラスライブラリjava.lang.reflect.Executableでは、ジェネリック型よりも特殊化された新しい型が導入されていMemberます。したがって、Java 8クラスライブラリでは、3項式の結果の型はExecutableではなくになりMemberます。

Java 8コンパイラの一部の(リリース前)バージョンではExecutable、三項演算子をコンパイルするときに、生成されたコードの内部への明示的な参照が生成されたようです。これにより、クラスのロードがトリガーさClassNotFoundExceptionれます。したがって、JDK≥8の場合にExecutableのみ存在するため、クラスライブラリ<JDK 8で実行すると、実行時に

この回答でTagir Valeevが述べたように、これは実際にはJDK 8のプレリリースバージョンのバグであり、修正されているため、if-else回避策と説明コメントの両方が廃止されています。

追記:このコンパイラのバグはJava 8より前に存在していたという結論に至るかもしれませんが、OpenJDK 7によって生成された3進数用のバイトコードは、OpenJDK 8によって生成されたバイトコードと同じです。実際、式は実行時にまったく言及されません。コードは実際には、テスト、ブランチ、ロード、リターンのみであり、追加のチェックは行われません。これは(もう)問題ではなく、確かにJava 8の開発中に一時的な問題であったようです。


1
次に、JDK 1.8でコンパイルしたコードをJDK 1.7で実行するにはどうすればよいですか。低いバージョンのJDKでコンパイルされたコードは、高いバージョンのJDKでも問題なく実行できることを知っています。
jddxf

1
@jddxf適切なクラスファイルバージョンを指定し、それ以降のバージョンでは使用できない機能を使用しない限り、すべてが問題ありません。問題は必ず発生しますが、そのような使用がこの場合のように暗黙的に発生する場合。
dhke

13
@ jddxf、-source / -target javacオプションを使用
Tagir Valeev

1
徹底的に説明してくれたdhkeとTagir Valeevに感謝します
jddxf

30

これは、公式のJDK-8リリースのほぼ1年前の2013年5月3日のかなり古いコミットで導入されました。コンパイラーは当時、激しい開発が行われていたため、このような互換性の問題が発生する可能性がありました。おそらく、SpringチームはJDK-8ビルドをテストし、実際にコンパイラーの問題であるにもかかわらず、問題を修正しようとしただけだと思います。JDK-8の公式リリースまでに、これは無関係になりました。これで、このコードの三項演算子は期待どおりに機能します(Executableコンパイルされた.class-fileのクラスへの参照はありません)。

現在、JDK-9でも同様のことが行われています。JDK-8でうまくコンパイルできるコードの一部は、JDK-9 javacでは失敗します。おそらく、そのような問題のほとんどはリリースまで修正されるでしょう。


2
+1。それで、これは初期のコンパイラのバグでしたか?参照されているその動作Executableは、仕様の一部の側面に違反していましたか?それとも、オラクルは、仕様に準拠し、下位互換性を損なうことなく、この動作を変更できることを認識しただけなのでしょうか。
ruakh

2
@ruakh、それはバグだったと思います。バイトコード(Java-8以前のいずれか)ではExecutable、中間の型に明示的にキャストする必要はまったくありません。Java-8では、式の型推論の概念が大幅に変更され、この部分が完全に書き直されたため、初期の実装にバグがあったことはそれほど驚くべきことではありません。
Tagir Valeev

7

主な違いは、3項(Javaでは条件演算子としてよく知られている)が式であるのに対し、if elseブロックはステートメントであるということです。

ステートメントは、のようなものを行うことができますreturnコントロールパスのいくつかの呼び出し元に。式は代入で使用することができます。

int n = condition ? 3 : 2;

したがって、条件の後の三項の2つの式は、同じ型に強制変換できる必要があります。これは、特にオートボクシングと自動参照キャストでJavaに奇妙な影響を与える可能性があります。これは、投稿されたコードのコメントが参照しているものです。あなたの場合の式の強制はjava.lang.reflect.Executable型になり(それが最も特殊な型であるため)、それは古いバージョンのJavaには存在しません。

文法的にはif else、コードがステートメントのような場合はブロックを使用し、式のような場合は3項を使用する必要があります。

もちろん、if elseラムダ関数を使用すると、ブロックを式のように動作させることができます。


6

3項式の戻り値の型は、Java 8で説明されているように変更された親クラスの影響を受けます。

キャストを作成できなかった理由がわかりません。

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