4

次の Java クラス定義を検討してください。

class Animal {}
class Lion extends Animal {}

Cages の共変を定義するときAnimal、Java で次のコードを使用します。

class Cage<T extends Animal> {
void add(T animal) { System.out.println("Adding animal..."); }
}

しかし、次のJavaの例...

public static void main(String... args) {
    Cage<? extends Animal> animals = null;
    Cage<Lion> lions = null;
    animals = lions;         // Works!
    animals.add(new Lion()); // Error!
}

... 次のエラーでコンパイルに失敗します:

Cage 型のメソッド add(capture#2-of ? extends Animal) は引数に適用できません (Lion)

Tigerそうしないと、後に追加さanimals = lionsれて実行時に失敗する可能性があるため、これが行われますか?

のサブタイプが 1 つしかない場合にそれを拒否しない特別な (仮想的な) ルールを作成できAnimalますか?

( を に置き換えることができることはわかっていますadd。)TAnimal

4

4 に答える 4

3

Javaの場合:

Cage<? extends Animal> animals = null;

これは檻ですが、どんな動物を受け入れるのかわかりません。

animals = lions;         // Works!

さて、あなたはケージ動物の種類について意見を追加しないので、ライオンは予想に違反しません。

animals.add(new Lion()); // Error!

あなたはケージの動物の種類がわからない。この特定のケースでは、ライオンを入れるライオンのケージになりますが、それを許可するルールでは、あらゆる種類の動物を任意のケージに入れることができます。適切に禁止されています。

Scalaでは:: extendsのCage[+T]場合、aは。と見なされます。BACage[B]Cage[A]

それを考えると、animals = lions許可されます。

ただし、これはjavaとは異なり、typeパラメーターは間違いなくAnimalワイルドカードではありません? extends Animal。に動物を入れることができCage[Animal]ます。ライオンは動物なので、ケージ[鳥]である可能性のあるケージ[動物]にライオンを入れることができます。これはかなり悪いです。

それが実際に許可されていないことを除いて(幸いなことに)。あなたのコードはコンパイルされるべきではありません(あなたのためにコンパイルされた場合、あなたはコンパイラのバグを観察しました)。共変ジェネリックパラメーターをメソッドの引数として表示することはできません。その理由は、それを許可するとライオンを鳥かごに入れることができるからです。T+Tはの定義のように表示さCageれますが、メソッドの引数として表示することはできませんadd

したがって、どちらの言語でも、ライオンを鳥かごに入れることは許可されていません。


更新された質問について。

そうでなければトラが追加される可能性があるため、それは行われますか?

はい、もちろんこれが理由です。型システムのポイントはそれを不可能にすることです。それは実行時エラーを引き起こしますか?おそらく、ある時点ではそうなりますが、実際のジェネリック型は実行時にチェックされないため(型消去)、addを呼び出した時点ではそうではありません。しかし、型システムは通常、エラーが発生したことを証明できるプログラムだけでなく、(ある種の)エラーが発生しないことを証明できないすべてのプログラムを拒否します。

動物のサブタイプが1つしかない場合に、それを拒否しない特別な(仮想の)ルールを作成できますか?

多分。あなたはまだ2種類の動物、すなわちAnimalとを持っていることに注意してくださいLion。したがって、重要な事実は、Lionインスタンスが両方のタイプに属しているということです。一方、Animalインスタンスはタイプに属していませんLionanimals.add(new Lion())許可される可能性があります(ケージは任意の動物用のケージ、またはライオン専用のケージのいずれかであり、どちらも問題ありません)が、許可されるanimals.add(new Animal())べきではありません(動物はライオン専用のケージである可能性があるため)。

しかしとにかく、それは非常に悪い考えのように聞こえます。オブジェクト指向システムの継承のポイントは、後で、他の場所で作業している他の誰かがサブタイプを追加できることです。これにより、正しいシステムが正しくなくなることはありません。実際、古いコードを再コンパイルする必要すらありません(おそらくソースがありません)。そのようなルールでは、それはもはや真実ではないでしょう

于 2012-08-01T14:03:06.530 に答える
1

このタイプで変数を宣言する場合: Cage<? extends Animal>; 基本的に、変数はから継承する不明なクラスを持つケージであると言っていますAnimal。それはまたはである可能性がありTigerますWhale; そのため、コンパイラには、を追加するのに十分な情報がありませんLionCage<Animal>必要なものを取得するには、変数をaまたは。として宣言しますCage<Lion>

于 2012-08-01T14:02:38.437 に答える
1

私はこの質問があなたのためにそれに答えるかもしれないと思います:

Javaジェネリック共分散

基本的に、Javaジェネリックは共変ではありません。

もちろん、これについて私が知っている最も良い説明は、Effective Java2ndEditionからのものです。

あなたはここでそれについて読むことができます:

http://java.sun.com/docs/books/effective/generics.pdf

実行時に仮想ルールを適用するのは非常に難しいと思います。コンパイラーは、リストに明示的に追加されたすべてのオブジェクトが実際に同じ種類の動物であるかどうかを理論的にチェックできますが、実行時にこれを壊す可能性のある条件があると確信しています。

于 2012-08-01T13:34:31.277 に答える
0

もしそうなら、それはおそらくScalaコンパイラのバグです。Oderskyetal。Scalaプログラミング言語の概要を書く:

Scalaの型システムは、型パラメーターが使用されている位置を追跡することにより、分散注釈が適切であることを保証します。これらの位置は、不変フィールドのタイプとメソッドの結果については共変として分類され、メソッドの引数のタイプとタイプのパラメーターの上限については反変として分類されます。非バリアント型パラメーターへの型引数は、常に非バリアント位置にあります。位置は、反変パラメーターに対応する型引数内の反変と共変の間でipsします。型システムは、共変(それぞれ、反変)型パラメーターが共変(反変)位置でのみ使用されることを強制します。

したがって、共変型パラメーターTは、反変の位置であるため、メソッド引数として表示してはなりません。

同様のルール(より特殊なケースで、この場合は問題になりません)は、Scala言語仕様(バージョン2.9)のセクション4.5にもあります。

于 2012-08-01T14:02:15.627 に答える