4

考虑以下 Java 类定义:

class Animal {}
class Lion extends Animal {}

Cage在为s定义协变时,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并在运行时失败?

如果只有一个子类型,是否可以制定一个不会拒绝它的特殊(假设)规则Animal

(我知道我可以将add'sT替换为Animal。)

4

4 回答 4

3

在 java 中:

Cage<? extends Animal> animals = null;

这是一个笼子,但你不知道它接受什么样的动物。

animals = lions;         // Works!

好吧,你没有添加关于什么样的笼子动物的意见,所以狮子不违反预期。

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

你不知道笼子里的动物是什么样的。在这种特殊情况下,它恰好是一个狮子笼,你把狮子放进去,很好,但允许这样做的规则只允许将任何种类的动物放入任何笼子。它是正确不允许的。

在 Scala:: Cage[+T]if BextendsA中,aCage[B]应该被认为是 a Cage[A]

鉴于此,animals = lions是允许的。

但这与java不同,类型参数肯定是Animal,而不是通配符? extends Animal。您可以将动物放入 a 中Cage[Animal],狮子是动物,因此您可以将狮子放入可能是笼子[鸟]的笼子[动物]中。这很糟糕。

除了它实际上是不允许的(幸运的是)。您的代码不应编译(如果它为您编译,则您观察到编译器错误)。协变泛型参数不允许作为方法的参数出现。原因正是允许它允许将狮子放在鸟笼中。它 T 出现+T在 的定义中Cage,它不能作为方法的参数出现add

所以两种语言都不允许把狮子放在鸟笼里。


关于您更新的问题。

这样做是因为否则可以添加老虎吗?

是的,这当然是原因,类型系统的重点是让它变得不可能。这会导致运行时错误吗?很有可能,它会在某个时候出现,但不是在你调用 add 的那一刻,因为在运行时不会检查泛型的实际类型(类型擦除)。但是类型系统通常会拒绝每个它不能证明(某种)错误不会发生的程序,而不仅仅是它可以证明它们确实发生的程序。

如果只有一个动物子类型,是否可以制定一个不会拒绝它的特殊(假设)规则?

也许。请注意,您仍然有两种类型的动物,即AnimalLion。所以重要的事实是一个Lion实例属于这两种类型。另一方面,Animal实例不属于 type Lionanimals.add(new Lion())可以允许(笼子可以是任何动物的笼子,或者只适合狮子,两者都可以),但animals.add(new Animal())不应该(因为动物只能是狮子的笼子)。

但无论如何,这听起来是个非常糟糕的主意。面向对象系统中的继承点是,某个时候,在其他地方工作的其他人可以添加子类型,这不会导致正确的系统变得不正确。事实上,旧代码甚至不需要重新编译(可能你没有源代码)。有了这样的规则,那将不再是真的

于 2012-08-01T14:03:06.530 回答
1

我认为这个问题可能会为您回答:

java泛型协方差

基本上,Java 泛型不是协变的。

我所知道的最好的解释当然来自 Effective Java 2nd Edition。

你可以在这里读到它:

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

我认为假设规则在运行时很难执行。编译器理论上可以检查显式添加到列表中的所有对象是否确实属于同一类型的动物,但我确信存在可能在运行时破坏它的条件。

于 2012-08-01T13:34:31.277 回答
1

当你用这种类型声明你的变量时:Cage<? extends Animal>; 你基本上是说你的变量是一个笼子,里面有一些未知的类,它继承自Animal. 它可以是 aTiger或 a Whale; 所以编译器没有足够的信息让你添加一个Lion。为了得到你想要的,你将你的变量声明为 aCage<Animal>或 a Cage<Lion>

于 2012-08-01T14:02:38.437 回答
0

如果是这样,那很可能是 Scala 编译器中的一个错误。奥德斯基等人。写在Scala 编程语言概述中

Scala 的类型系统通过跟踪使用类型参数的位置来确保方差注释是正确的。对于不可变字段和方法结果的类型,这些位置被归类为协变的,而对于方法参数类型和上类型参数边界而言,这些位置是逆变的。非变体类型参数的类型参数始终处于非变体位置。对应于逆变参数的类型参数内的逆变和协变之间的位置 ips。类型系统强制协变(分别为逆变)类型参数仅用于协变(逆变)位置。

因此,协变类型参数 T 不能作为方法参数出现,因为那是逆变位置。

Scala 语言规范(版本 2.9)第 4.5 节中也存在类似的规则(有更多特殊情况,在这种情况下都不重要)。

于 2012-08-01T14:02:15.627 回答