2

このコードをチェックしてください。

// 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
  }

私はそれを解決する方法を知っています。なぜそれが起こるのか知りたいだけです。コンパイラは単にすべての可能な出力をチェックするべきではありませんか?

4

5 に答える 5

8

式の静的な結果タイプは、 Object(i2 == null) ? i1 : i2の共通の祖先でi1あり、i2これはObjectです。forステートメントでは、式の静的型がまたは配列型である必要がありますIterable。そうではないため、コンパイルエラーが発生します。

ここで、なぜコンパイラがそれ(i2 == null) ? i1 : i2が常に配列またはIterableであると推測しないのかを尋ねている場合は、次のようになります。

  1. Java言語仕様(JLS)ではこれは許可されていません。
  2. JLSがそれを許可した場合(ただし、それを必要としない場合)、定理証明がどれだけ優れているかに応じて、コンパイラーごとに動作が異なります。悪い。
  3. JLSがそれを必要とする場合、コンパイラーは洗練された定理証明器`1 (BAD SLOW)を組み込む必要があり、停止性問題2(BAD BAD BAD)を解決するという「小さな不便」に遭遇します。
  4. 実際には、コンパイラは、それぞれ異なるコードを生成する必要があるため、式が2種類のタイプのどちらを持っているかを知る必要があります。

仮に、Java型システムが少し異なっていれば、この特定のケースをより適切に処理できます。具体的には、Javaが代数的データ型oをサポートしている場合、「オブジェクト配列または反復可能」として宣言することが可能であり、forループは型チェック可能です。


1-oとして初期化されたとしo = (x * x < 0) ? new Object() : new Object[0]ます。それが常にインスタンスになると判断するにObject[]は、(実)数の2乗が負ではないという事実を含む小さな証明が必要です。これは単純な例であり、任意の難しい証明を必要とする任意の複雑な例を作成することができます。

2-停止性問題は数学的に計算不可能な関数であることが証明されています。つまり、終了するかどうかを数学的に証明できない関数が存在します。

于 2009-08-18T03:47:23.857 に答える
2

Stephen 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");
}

これはtest()の出力です:

method1(Object) called
method1(Object) called

メソッドのオーバーロードはコンパイル時に行われるため、オペランドの型が異なるため、三項演算子式の静的型はObjectであることがわかります。したがって、あなたがするとき:

for (Object o_ : i2 == null ? i1 : i2)

あなたは本当にコンパイラにオブジェクト上でforeachループを生成するように要求していますが、これは違法です。

于 2009-08-18T04:14:26.207 に答える
1

この場合、条件式は2つのタイプの最小の上限のタイプであるですObject。foreachループはタイプでは機能しません。Object

于 2009-08-18T03:45:18.947 に答える
0

Object []をArrays.asList(Object [])でラップし、すべての場合にIterableを使用することで、コードの重複を回避できます。はい、アレイでの作業よりも少し遅いですが、DRYであるという利点があります。これは、常に最初の近似であるIMHOである必要があります。

したがって、次のような結果になります。

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_);
}

簡単にするために(コンパイラの作成者にとってどのように簡単かについてはStephen Cの回答を参照してくださいが、おそらく言語設計も簡単です)三項演算子は、1つを収容するのではなく、2つの戻り型の中で最も低い共通のデモニネータであると想定します。 2つのリターンタイプの。メソッドのオーバーロードの場合を考えてみましょう。適切な方法が決定され、コンパイル時間が決定されます。これは、コンパイラがコンパイル時に宣言された戻り型について決定できる必要があり、実行時にその決定を遅らせることができないことを意味します。

于 2009-08-18T04:18:00.957 に答える
0

実際には、2つのオーバーロードされたdeepPrint(Object [])メソッドとdeepPrint(Iterator i)メソッドを追加し、deepPrint(Objectオブジェクト)でディスパッチを実行する必要があります。はい、for / eachループの動作の性質上、小さな変更を加えて同じコードをコピーして貼り付ける必要があります。

それらすべてを1つの大きな方法にまとめようとするのは匂いです。

于 2009-08-18T03:50:11.823 に答える