なぜこれは Java が無効なのでしょうか?三項演算子の出力の種類
-
18-09-2019 - |
質問
このコードをチェックしてください。
// Print object and recurse if iterable
private static void deep_print(Object o) {
System.out.println(o.getClass().toString() + ", " + o.toString());
boolean iter = false;
Iterable<?> i1 = null;
Object[] i2 = null;
if (o instanceof Iterable<?>) {
iter = true;
i1 = (Iterable<?>) o;
} else if (o instanceof Object[]) {
iter = true;
i2 = (Object[]) o;
}
if (iter) {
for (Object o_ : i2 == null ? i1 : i2) deep_print(o_); // ERROR: Can only iterate over an array or an instance of java.lang.Iterable
}
私はそれを解決する方法を知っています。なぜそうなるのか知りたいだけです。コンパイラは考えられる出力をすべてチェックするだけでよいのではないでしょうか?
解決
式の静的な結果の型 (i2 == null) ? i1 : i2
~の共通の祖先です i1
そして i2
それがオブジェクトです。あ for
ステートメントには次のことが必要です 静的型 式のいずれかになります Iterable
または配列型。そうでないため、コンパイル エラーが発生します。
なぜコンパイラがそれを推測しないのかと言うと、 (i2 == null) ? i1 : i2
常に配列または Iterable になります。
- Java 言語仕様 (JLS) ではこれが許可されていません。
- JLS がそれを許可している (ただし、要求していない) 場合、コンパイラーが定理証明の能力に応じて、異なる動作をすることになります。悪い。
- JLS がそれを必要とした場合、コンパイラーは高度な定理証明器を組み込む必要があります。`1 (BAD SLOW)、停止問題を解決する際に「ちょっとした不便」に遭遇することになります。2 (悪い、悪い、悪い)。
- 実際には、コンパイラーはそれぞれの場合に異なるコードを生成する必要があるため、式が 2 種類の型のどちらであるかを知る必要があります。
仮に、Java の型システムが少し異なっていたとすると、この特定のケースでは、 できた より良く扱われるようになります。具体的には、Java がサポートされている場合 代数データ型 そうすれば宣言できるでしょう o
「オブジェクト配列または反復可能オブジェクトのいずれか」として ...そしてその for
ループは型チェック可能になります。
1 - 次のように仮定します。 o
として初期化されていました o = (x * x < 0) ? new Object() : new Object[0]
. 。それが常に結果として起こると判断すると、 Object[]
この例では、(実数) の 2 乗が負ではないという事実を含む小さな証明が必要です。これは単純な例ですが、任意の難しい証明を必要とする任意の複雑な例を構築することが可能です。
2 - 停止問題は計算不可能な関数であることが数学的に証明されています。つまり、終了するかどうかを数学的に証明できない関数が存在します。
他のヒント
スティーブン・Cの答えを説明するために、次のコードを考えてみます:
void test() {
Iterable<Integer> i1 = new ArrayList<Integer>();
Object[] i2 = { 1, 2, 3 };
method1(false ? i1 : i2);
method1(true ? i1 : i2);
}
void method1(Object o) {
System.out.println("method1(Object) called");
}
void method1(Object[] o) {
System.out.println("method1(Object[]) called");
}
void method1(Iterable<?> o) {
System.out.println("method1(Iterable<?>) called");
}
この試験の出力は、()
method1(Object) called
method1(Object) called
メソッドのオーバーロードは、コンパイル時に行われるため、オペランドの型が異なるため、は、あなたが、三項演算子式の静的な型がオブジェクトであることがわかります。そのため、あなたはときます:
for (Object o_ : i2 == null ? i1 : i2)
あなたが本当に違法である、オブジェクト、オーバーforeachループを生成するコンパイラを求めています。
この場合には、条件式Object
あり、foreachループは、タイプObject
で動作しない
あなたは実際には2つのオーバーロードさdeepPrint(オブジェクト[])とdeepPrint(イテレータI)メソッドを追加し、deepPrint(Objectオブジェクト)での発送を行う必要があります。はいなぜならあなたは小さな変更で同じコードをコピーして貼り付ける必要がありますどのように機能するかについて/各ループの性質ます。
一つの大きな方法にすべてのことを置くしようとすると香りです。
あなたはは、Arrays.asList(オブジェクト[])で[]オブジェクトをラップすることにより、コードの重複を避けるため、その後、すべての場合に反復処理可能を持つことができます。はい、そのアレイ上の作業よりもわずかに遅いが、それは常に最初の近似であるべき、DRYであるという利点を有している、私見。
ですから、このようなものに終わるだろう
Iterable<?> it = null;
if (o instanceof Iterable<?>) {
it = (Iterable<?>) o;
} else if (o instanceof Object[]) {
it = Arrays.asList((Object[]) o);
}
if (it != null) {
for (Object o_ : it) deep_print(o_);
}
簡単のため(それはコンパイラの作家のために簡単ですが、それは同様に、おそらく単純な言語設計がいかにのためのスティーブン・Cの回答を参照してください)三項演算子ではなく、2つの戻りタイプの間の最小公demoninatorするタイプを想定しています2つのリターンタイプのいずれかをaccomidatingより。メソッドのオーバーロードのケースを考えてみましょう。右の方法が決定され、時間をコンパイルされます。これは、コンパイラがコンパイル時に宣言された戻り値の型についての決定を行うことができなければならない、と時間を実行するために、その決定を遅らせることができないことを意味します。