协变结构在Java中捕获错误失败

时间:2012-08-01 13:24:04

标签: java generics inheritance covariance bounded-wildcard

考虑以下Java类定义:

class Animal {}
class Lion extends Animal {}

Cage定义协变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!
}

...无法编译并出现以下错误:

  

方法添加(捕获#2-of?extends Animal)   在凯奇类型   不适用于参数(Lion)

是否这样做是因为在Tiger之后可以添加animals = lions之类的其他类型并在运行时失败?

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

(我知道我可以用add替换T的{​​{1}}。)

4 个答案:

答案 0 :(得分:3)

在java中:

Cage<? extends Animal> animals = null;

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

animals = lions;         // Works!

好的,你没有添加关于什么样的笼养动物的意见,所以狮子没有违背期望。

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

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

在Scala中: Cage[+T]:如果B延长A,那么Cage[B]应被视为Cage[A]

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

但这与java不同,type参数绝对是Animal,而不是通配符? extends Animal。你可以把动物放在Cage[Animal]中,狮子是动物,所以你可以把狮子放在笼子[动物]中,它可能是一只笼子[Bird]。这非常糟糕。

除非事实上不允许(幸运的是)。您的代码不应该编译(如果它为您编译,您发现编译器错误)。协变通用参数不允许作为方法的参数出现。原因恰恰在于允许将狮子放入鸟笼中。它在+T的定义中显示为Cage,它不能作为方法add的参数出现。

所以这两种语言都不允许将狮子放入鸟笼。


关于您更新的问题。

是否完成了因为否则可以添加老虎?

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

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

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

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

答案 1 :(得分:1)

我认为这个问题可能会为你解答:

java generics covariance

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

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

你可以在这里阅读:

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

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

答案 2 :(得分:1)

当您使用以下类型声明变量时:Cage<? extends Animal>;你基本上说你的变量是一个笼子,里面有一些继承自Animal unkown 类。它可以是TigerWhale;所以编译器没有足够的信息让你添加Lion。要获得所需内容,请将变量声明为Cage<Animal>Cage<Lion>

答案 3 :(得分:0)

如果是这样,那可能是Scala编译器中的一个错误。奥德斯基等人。写在An Overview of the Scala Programming Language

  

Scala的类型系统确保方差注释   通过跟踪类型pa-的位置发出声音   使用rameter。这些职位被归类为协变   对于不可变的elds和方法结果的类型,和   方法参数类型和上限类型的逆变量   参数范围。键入非变体类型的参数   参数始终处于非变量位置。这个位置   在类型参数内的对立和共变体之间的ips   这对应于逆变参数。类型   系统强制执行协变(分别为逆变)   类型参数仅用于协变(逆变)   位置。

因此,协变类型参数T不得作为方法参数出现,因为这是一个逆变位置。

类似的规则(更多特殊情况,在这种情况下都不重要)也出现在Scala Language Specification (version 2.9)第4.5节中。