4

私はJava 8を使用しています.Java OCP 8に合格するためのトレーニング中に、理解できないコードの断片を見つけて知りたいのですが、なぜそれが私にとって奇妙であるか.

次の階層があります。

class A {}
class B extends A {}
class C extends B {}

最初のもの、このコードは仕事です:

List<?> list1 = new ArrayList<A>() { 
    { 
        add(new A());
    }
};

しかし、次のコードは動作しません、コンパイル エラー:

list1.add(new A());

では、なぜこの方法で新しいレコードを追加できないのでしょうか?

2つ目、このコードは機能します。

List<? extends A> list2 = new ArrayList<A>() {
    {
        add(new A());
        add(new B());
        add(new C());
    } 
};

しかし、次のコードは動作しません、コンパイル エラー:

list2.add(new A());
list2.add(new B());
list2.add(new C());

最後に、このコードは機能します:

List<? super B> list3 = new ArrayList<A>() {
    {
        add(new A());
        add(new B());
        add(new C());
    }
};

しかし、次のコードでは、new A()を追加すると、コンパイル エラーが発生します。

list3.add(new A()); // compilation error
list3.add(new B());
list3.add(new C());

回答ありがとうございます。

4

2 に答える 2

4

これは、タイプ セーフを強制するように設計されたコンパイル エラーです。コンパイラがそれを許可した場合、何が起こるか想像してみてください:

問題 1 では、オブジェクトlist1が宣言されると、コンパイラは宣言された型のみを考慮し、List<?>それが最後に に割り当てられたという事実を無視しArrayList<A>ます。

List<?> list1 = ...;  // The compiler knows list1 is a list of a certain type
                      // but it's not specified what the type is. It could be
                      // a List<String> or List<Integer> or List<Anything>
list1.add(new A());   // What if list1 was e.g. a List<String>?

しかし:

List<?> list1 = new ArrayList<A>() { 
    { 
        add(new A());
    }
};

ここでは、式に代入してlist1います。式自体、つまり の後=のすべては を使用せず、実際には拡張され、呼び出しを行う初期化ブロックを持つ?匿名クラスであり、これは問題ありません。ArrayList<A>add(new A())

2 番目の問題 (list2 を使用) も同じ原因です。

第三号では、

List<? super B> list3 = new ArrayList<A>() {
    {
        add(new A());
        add(new B());
        add(new C());
    }
};

list3.add(new A()); // compilation error

コンパイラlist3List<? super B>. これは、ジェネリック パラメータがBまたはそのスーパークラスである可能性があることを意味しますA。だとしたらList<B>Aに を追加することはできませんList<B>。したがって、コンパイラはこのコードを拒否します。

于 2016-10-21T12:44:18.637 に答える
1

簡単な答え (「なぜそれが変なのか」) は、特定の Java 略記法を使用すると、2 つのコードが非常に似ているように見えるにもかかわらず、実際には非常に異なっているということです。

したがって、「これが機能する場合は、これも機能するはずだ」と思われるかもしれませんが、2 つのコードの重要な違いが曖昧になっているため、そうではありません。

いくつかのコードを個別に見てみましょう。

public interface ListFactory {
    List<?> getList();
}

だから誰かが私たちにList<?>. 次のように使用できます。

List<?> list1 = myListFactory.getList();

しかし、もしそうなら

list1.add(new A());

を返す ListFactory 実装をたまたま取得したかどうかに依存するため、コンパイラはこれが合法であることを証明できませList<A>List<String>

上記を置き換えるとどうなりますか

List<?> list1 = new SpecialList();
list1.add(new A());

これは元のコードに少し近いです。しかし、コンパイラには同じ問題が存在します。評価時にlist1.add(new A());、list1 への代入の履歴から手がかりを探しません。list1 のコンパイル時の参照型が であることだけを知っているため、以前は不正だっList<?>た場合でも、ここでも不正です。list1.add(new A());

(一見すると、これはコンパイラーの欠点のように思えるかもしれませんが、そうではないと思います。一般に、参照型が直接伝える以上のことをコンパイラーが試して知ることは、実用的でも望ましいことでもありません。add(new A())別の参照型を使用できるようにオブジェクトを参照するには、たとえばList<A> list2 = new SpecialList();.)

上記は、SpecialList.java があることを意味します。それを見てみましょう:

public class SpecialList extends ArrayList<A> {
    public SpecialList() {
        add(new A());
    }
}

これは少しばかげているように思えるかもしれませんが、コンパイラに関する限り (A が定義され、java.util.ArrayList がインポートされている限り)、何も問題がないことはおそらく驚くことではありません。

add(new A());この場合、 は の省略形であることに注意してくださいthis.add(new A());。SpecialList() コンストラクターでthisは、拡張するように宣言されているタイプ SpecialList の参照でありArrayList<A>、確かにadd(new A())のサブクラスにできArrayList<A>ます。

これで、元のコードのようなものを作成するためのすべてのピースが揃いました。

List<?> list1 = new ArrayList<A>() {
    {
        add(new A());
    }
}
list1.add(new A());

ここでの 3 行目と 6 行目は、Java シンタックス シュガーを適用したため、非常によく似ています。しかし、3 行目は SpecialList() の例とまったく同じです。 add() が呼び出される参照型は、 の無名サブクラスですArrayList<A>。しかし、6 行目は見た目とほぼ同じなので、最初の 2 つの例と同じ理由で失敗します。

同様の分析は、あなたが見ている他の奇妙な違いを説明します.

于 2016-10-21T15:51:46.947 に答える