协变

Java编程思想学习笔记

协变

协变与数组

class Fruit {} class Apple extends Fruit {} class Jonathan extends Apple {} class Orange extends Fruit {}

public class CovariantArrays { public static void main(String[] args) { Fruit[] fruit = new Apple[10]; fruit[0] = new Apple(); // OK fruit[1] = new Jonathan(); // OK // Runtime type is Apple[], not Fruit[] or Orange[] try { // compiler allows you to add Fruit; fruit[0] = new Fruit(); // ArrayStoreException } catch(Exception e) { System.out.println(e); } try { // compiler allows you to add Fruit; fruit[0] = new Orange(); // ArrayStoreException } catch(Exception e) { System.out.println(e); } } }

代码新建了一个Apple数组,并将其赋值给一个Fruit数组引用。因为Apple继承自Fruit,一个Apple数组也应该是一个Fruit数组,因此这个赋值没有问题。 但是数组的实际类型还是Apple[],因此,只能往其中放置Apple或Apple的子类型。在向该数组中放置Fruit或是Orange类型时,编译器并没有报错,原因是 将数组Apple[]手动向上转型为Fruit[],因此,在编译器角度看来,自然可以接收Fruit或是Fruit的子类型。然而,在运行期间,数组的机制知道它处理的 是Apple[],因此在放置其他类型时会抛出异常。

数组对象能够保留有关他包含的对象的类型。作为对比,查看泛型容器的行为

public class NonCovariantGenerics {
	// Compile Error: incompatible types;
	List<Fruit> flist = new ArrayList<Apple>();
}

上面的代码无法通过编译,原因是“无法将一个涉及Apple的泛型赋值给一个涉及Fruit的泛型”。尽管Apple是Fruit的子类,但是此处,我们比较的是“容器的类型”, 而非“容器持有的类型”,因此无法将一个Apple的List赋值给一个Fruit的List。(Apple继承自Fruit,但是List<Apple>并没有继承自List<Fruit>)

协变与泛型

与数组不同,泛型没有内建的协变类型。如果想要在两个类型之间建立某种类型的向上转型关系,需要使用通配符,如下:

public class GenericsAndCovariance {
	public static void main(String[] args) {
		// Wildcards allow convariance
		List<? extends Fruit> flist = new ArrayList<Apple>();
		// compile Error: can't add any type of object
		// flist.add(new Apple());
		// flist.add(new Fruit());
		// flist.add(new Object());
		flist.add(null);				// legal but uninteresting
		Fruit f = flist.get(0);
	}
}

flist的类型时List<? extends Fruit>,可以读作“具有任何从Fruit继承的类型的列表”。然而,此时虽然赋值成功了,但却不能向这个List中添加任何类型的Fruit。 对于flist,编译器知道其中存放的是某种Fruit类型,却不知道具体是哪种Fruit类型。因此编译器具体向其中添加任何对象。但是,调用一个返回Fruit的方法却是被 允许的,因为编译器知道这个List中的对象,至少具有Fruit的类型,所以这么做是安全的

通过一个简单的Holder类来进一步查看:

public class Holder<T> {
	private T value;
	public Holder() {}
	public Holder(T val) { value = val; }
	public void set(T val) { value = val; }
	public T get() { return value; }
	public boolean equals(object obj) {
		return value.equals(obj);
	}

	public static void main(String[] args) {
		Holder<Apple> apple = new Holder<Apple>(new Apple());
		Apple d = apple.get();
		Apple.set(d);
		// Holder<Fruit> fruit = apple;		// can't upcast
		Holder<? extends Fruit> fruit = apple;	// OK
		Fruit p = fruit.get();
		d = (Apple)fruit.get();					// return Ojbect
		try {
			Orange c = (Orange)fruit.get();		// no warning
		} catch(Exception e) { System.out.println(e); }
		// fruit.set(new Apple());				// cannot call set()
		// fruit.set(new Fruit());				// cannot call set();
		System.out.Println(fruit.equals(d));	// OK
	}
}

如上,如果创建了一个Holder<Apple>,不能将其向上转型为Holder<Fruit>,但是可以将其向上转型为Holder<? extends Fruit>。如果调用get(), 它只会返回一个Fruit——这就是在给定“任何扩展自Fruit的对象”这一边界之后,它所能知道的一切了。你可以将get()的返回结果转型为任何某种具体的Fruit类型, 并且不会得到警告,但是运行时可能会得到ClassCastException的异常。

set方法不能工作于Apple或Fruit,因为set的参数也是"? extends Fruit",这意味着它可以是任何继承自Fruit的事物,此时编译器无法验证其安全性,因此调用set() 方法会导致编译不过。

equals()方法工作良好,因为它接收Object类型而非T类型的参数,不需要判断其合法性。

猜你喜欢

转载自my.oschina.net/u/2561528/blog/1796311