8

誰がこれを説明できますか?

私はこれらのクラスのカップルを持っています:

abstract class Animal {
    public void eat() {
        System.out.println("Animal is eating");
    }
}

class Dog extends Animal {
    public void woof() {
        System.out.println("woof");
    }
}

class Cat extends Animal {
    public void meow() {
        System.out.println("meow");
    }
}

そして、これはアクションです:

import java.util.ArrayList;
import java.util.List;

public class TestClass {

    public static void main(String[] args) {
        new TestClass().go();
    }

    public void go() {
        List<Dog> animals = new ArrayList<Dog>();
        animals.add(new Dog());
        animals.add(new Dog());
        doAction(animals);
    }

    public <T extends Animal> void doAction(List<T> animals) {

        animals.add((T) new Cat()); // why is it possible? 
                                    // Variable **animals** is List<Dog>, 
                                    // it is wrong, that I can add a Cat!

        for (Animal animal: animals) {
            if (animal instanceof Cat) {
                ((Cat)animal).meow();
            }
            if (animal instanceof Dog) {
                ((Dog)animal).woof();
            }
        }
    }
}

この例はエラーなしでコンパイルされ、出力は次のとおりです。

woof
woof
meow

しかし、どうすれば犬猫のリストに追加できますか?そして、猫はどのように犬にキャス​​トされますか?

私が使用しているのは、Javaバージョン「1.6.0_24」です。OpenJDKランタイム環境(IcedTea6 1.11.1)(6b24-1.11.1-4ubuntu3)

4

6 に答える 6

7

ジェネリックの扱いは次のとおりです(ジェネリックは消去によって機能するため、キャストハッカリーを使用するものは実行時に安全ではない可能性があります):

同じ方法でパラメータ化されたサブタイプを割り当てることができます。

List<Animal> l = new ArrayList<Animal>();

このパラメータまたはそのサブクラスのタイプであるアイテムを追加できます。

l.add(new Cat());
l.add(new Dog());

ただし、パラメータのタイプのみを取得できます。

Animal a = l.get(0);
Cat c = l.get(0); //disallowed
Dog d = l.get(1); //disallowed

これで、ワイルドカードを使用してパラメータタイプの上限を設定できます

List<? extends Animal> l = new ArrayList<Animal>();
List<? extends Animal> l = new ArrayList<Cat>();
List<? extends Animal> l = new ArrayList<Dog>();

ただし、このリストに新しいアイテムを追加することはできません

l.add(new Cat()); // disallowed
l.add(new Dog()); // disallowed

あなたの場合、あなたはそれを持っているので、あなたがにキャストした場合に追加できるList<T>メソッドを持っています。ただし、タイプは上で制限されているため、このリストに追加しようとしないでください。ただし、具体的なタイプとして扱われるため、キャストが許可されます。ただし、これはをスローする可能性があります。 add(T t)TTAnimalClassCastException

そして、あなたは上限タイプであるアイテムだけを検索することができます

Animal a = l.get(0);
Cat c = l.get(0); //disallowed
Dog d = l.get(1); //disallowed

または、下限パラメータタイプを設定できます

List<? super Animal> l1 = new ArrayList<Object>();
List<? super Animal> l1 = new ArrayList<Animal>();
List<? super Cat> l2 = new ArrayList<Animal>();
List<? super Cat> l2 = new ArrayList<Cat>();
List<? super Dog> l3 = new ArrayList<Animal>();
List<? super Dog> l3 = new ArrayList<Dog>();

また、下限タイプのサブタイプであるオブジェクトを追加できます

l1.add(new Cat());
l1.add(new Dog());
l1.add(new Object()); //disallowed

ただし、取得されるすべてのオブジェクトはObject型です。

Object o = l1.get(0);
Animal a = l1.get(0); //disallowed
Cat c = l2.get(0); //disallowed
Dog d = l3.get(0); //disallowed
于 2012-05-24T20:13:18.703 に答える
2

ジェネリックスがランタイムタイプチェックを実行することを期待しないでください。コンパイル中に、Javaはすべての型推論を実行し、すべての型をインスタンス化し、...そしてコードからジェネリック型のすべてのトレースを消去します。実行時のタイプはListであり、List<T>またはList<Dog>ではありません。

主な質問は、チェックされていない変換に関する警告だけで、new Cat()タイプにキャストできる理由ですが、有効です。T extends Animal型システムの特定の不健全な機能により、そのような疑わしいキャストを合法化する必要があります。

コンパイラがリストに何も追加されないようにする場合は、ワイルドカードを使用する必要があります。

public void doAction(List< ? extends Animal > animals) {
    animals.add(new Cat()); // disallowed by compiler
    animals.add((Animal)new Cat()); // disallowed by compiler

    for (Animal animal: animals) {
        if (animal instanceof Cat) {
            ((Cat)animal).meow();
        }
        if (animal instanceof Dog) {
            ((Dog)animal).woof();
        }
    }
}

PSループ本体の疑わしいダウンキャストは、非交和(バリアント)型に対してJavaがいかに不完全であるかを示す完璧な例です。

于 2012-05-24T19:26:50.937 に答える
1

これは型消去に関連しています。タイプは実行時に保持されません。実際、Listは実行時にTypeObjectのリストになります。これが、コンパイラの警告が表示されるか、animals.add((T)new Cat());にある必要がある理由です。コンパイル時に、CatはタイプTの動物を拡張します。ただし、その時点でDogがリストに含まれていることを強制することはできないため、コンパイラーは警告します。

于 2012-05-24T19:21:15.420 に答える
0

ジェネリックは、コンパイル時の安全性のためにのみ機能します。あなたの場合、コンパイラはどうやって何か悪いことが起こるかを知ることができますか?それはあなたの型定義を仮定し、それから進みます。より多くのことを行うことは、コンパイラーにとってはるかに手の込んだ作業を意味しますが、この実行前をキャッチできる拡張静的チェッカーやその他のツールがあります。

于 2012-05-24T19:16:33.363 に答える
0

Tキャストが成功できるようなものがあると言えば十分です。コンパイルされたコードは、あなたが書いたのとまったく同じになりますanimals.add((Animal)new Cat());

于 2012-05-24T19:21:57.473 に答える
0

関数は、クラスを拡張doActionする型によってパラメーター化されます。したがって、タイプまたはのオブジェクトにすることができます。また、正式なパラメータ有効なパラメータ の違いにも注意する必要があります。仮パラメーターは、メソッドを定義するときに使用するパラメーターであり、この場合は(ショートカットとして使用します)。有効なパラメータは、メソッドを呼び出すときにメソッドに「与える」パラメータです。ここでは。。 で 見てみましょう。動物は、要素がまたはのいずれかであるタイプのリストであり、それが正式なパラメーターのタイプであるため、間違いはありません。TAnimalDogCat

List <T>
List<Dog>

animals.add((T) new Cat())doAction()TDogCat
それが理由です。これは、パラメータ化クラスを使用する利点の1つです。

于 2012-05-24T19:33:00.220 に答える