java 泛型简谈(二)

子类型
在Java中,跟其它具有面向对象类型的语言一样,类型的层级可以被设计成这样:
                                                                  Object
                                                                       |
                                                                       |
                                                                Fruit(水果)

                                                                      |
                                                             -------------------
                                                             |                      |
                                                    Apple(苹果)    Banana(香蕉)
                                                             |
                                              FujiApple(富士苹果)
   
在Java中,类型T的子类型既可以是类型T的一个扩展,也可以是类型T的一个直接或非直接实现(如果T是一个接口的话)。因为“成为某类型的子类型”是一个具有传递性质的关系,如果类型A是B的一个子类型,B是C的子类型,那么A也是C的子类型。在上面的图中:
FujiApple(富士苹果)是Apple的子类型
Apple是Fruit(水果)的子类型
FujiApple(富士苹果)是Fruit(水果)的子类型

所有Java类型都是Object类型的子类型。

父类型的任何一个子类型都可以被赋给一个父类型的声明:
 FujiApple fa = new FujiApple();
 Apple a = fa;
 Fruit f = a;

package lucky678.generics;
/**
 * 水果类
 * @author Jony
 *
 */
public class Fruit {
	
}

package lucky678.generics;
/**
 * 苹果类 继承水果类
 * @author Jony
 *
 */
public class Apple extends Fruit {

}

package lucky678.generics;
/**
 * 富士苹果  苹果子类
 * @author Jony
 *
 */
public class FujiApple extends Apple {

}

package lucky678.generics;
/**
 * 香蕉类  继承水果类
 * @author Jony
 *
 */
public class Banana extends Fruit {

}

泛型类型的子类型
如果一个Apple对象的实例可以被赋给一个Fruit对象的声明,就像上面看到的,那么,List<Apple> 和 List<Fruit>之间又是个什么关系呢?
更通用些,如果类型A是类型B的子类型,那C<A> 和 C<B>之间是什么关系?

答案会出乎你的意料:没有任何关系。用更通俗的话,泛型类型跟其是否子类型没有任何关系。

这意味着下面的这段代码是无效的:
List<Apple> list1 = new ArrayList<Apple>();
List<Fruit> list2 = list1; (错误的)

为什么?一个苹果是一个水果,为什么一箱苹果不能是一箱水果?
在某些事情上,这种说法可以成立,但在类型(类)封装的状态和操作上不成立。如果把一箱苹果当成一箱水果会发生什么情况?
List<Apple> list1 = new ArrayList<Apple>();
List<Fruit> list2 = list1;
list2.add(new Banana());
水果集合中添加了香蕉对象  而list1一箱苹果中是不能添加香蕉的,这是绝对不允许的。
更直观的理解也就是说:一箱水果不是一箱苹果,因为它有可能是一箱另外一种水果,比如香蕉(子类型)。

注意:
对于数组,它们和子类型是相关的:如果类型A是类型B的子类型,那么A[]是B[]的子类型:
Apple[] apples = new Apple[1];
Fruit[] fruits = apples;//正确
fruits [0] = new Banana();//编译时正确
但是在运行时需要检查类型,产生java.lang.ArrayStoreException异常。

package lucky678.generics;

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

/**
 * 父子类型  泛型使用的关系
 * @author Jony
 *
 */
public class Gen4 {

	public static void main(String[] args) {
		List<Apple> list1 = new ArrayList<Apple>();
		
		//错误在编译时就报错
		//List<Fruit> list2 = list1; //(错误的,一箱水果(集合)不能直接说成一箱苹果(集合 ))
		//list2.add(new Banana());//如果上面正确,苹果集合中就添加了香蕉
				
		//编译时 正确   
		Apple[] apples = new Apple[1];
		Fruit[] fruits = apples;//正确
		
		/*
		 * 但是在运行时需要检查类型
		 * java.lang.ArrayStoreException异常
		 */
		fruits [0] = new Banana();
		
	}
}

通配符
前面的部分里已经说过了泛型类型的子类型的不相关性。但有些时候,我们希望能够像使用普通类型那样使用泛型类型:
1、向上造型一个泛型对象的引用
2、向下造型一个泛型对象的引用

 

向上造型一个泛型对象的引用
例如,假设我们有很多箱子,每个箱子里都装有不同的水果,我们需要找到一种方法能够通用的处理任何一箱水果。更通俗的说法,A是B的子类型,我们需要找到一种方法能够将C<A>类型的实例赋给一个C<B>类型的声明。
为了完成这种操作,我们需要使用带有通配符的扩展声明,就像下面的例子里那样:

List<Apple> apples = new ArrayList<Apple>();
List<? extends Fruit> fruits = apples;
“? extends”是泛型类型的子类型相关性成为现实:Apple是Fruit的子类型,List<Apple> 是 List<? extends Fruit> 的子类型。

上面的代码是正确的,类似父子类型数组的操作,但是泛型可以在编译时直接检查类型,例如:
fruits.add(new Banana());编译错误
即使你往里加入Fruit对象也不行:
fruits.add(new Fruit()); 编译错误

你没有办法做到这些。事实上你不能够往一个使用了? extends的数据结构里写入任何的值。
原因非常的简单,你可以这样想:这个? extends T 通配符告诉编译器我们在处理一个类型T的子类型,但我们不知道这个子类型究竟是什么。因为没法确定,为了保证类型安全,我们就不允许往里面加入任何这种类型的数据。另一方面,因为我们知道,不论它是什么类型,它总是类型T的子类型,当我们在读取数据时,能确保得到的数据是一个T类型的实例:
Fruit get = fruits.get(0);

package lucky678.generics;

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

/**
 * 通配符
 * 向上造型一个泛型对象的引用     ? extends T
 * @author Jony
 *
 */
public class Gen5 {

	public static void main(String[] args) {
		List<Apple> apples = new ArrayList<Apple>();
		/*
		 *“? extends”是泛型类型的子类型相关性成为现实:
		 *Apple是Fruit的子类型,
		 *List<Apple> 是 List<? extends Fruit> 的子类型。 
		 */
		List<? extends Fruit> fruits = apples;
		
		/*
		 * 不能够往一个使用了? extends 的数据结构里写入任何的值
		 */
		//fruits.add(new Banana());//错误   
		//fruits.add(new Fruit());//错误
		//fruits.add(new Apple());//错误
	}

}

向下造型一个泛型对象的引用
现在我来介绍另外一种通配符:? super。如果类型B是类型A的超类型(父类型),那么C<B> 是 C<? super A> 的子类型:
List<Fruit> fruits = new ArrayList<Fruit>();
List<? super Apple> apples= fruits;

我们看到fruits指向的是一个装有Apple的某种超类(supertype)的集合apples。同样的,我们不知道究竟是什么超类,但我们知道Apple和任何Apple的子类都跟它的类型兼容。
既然这个未知的类型即是Apple,也是FujiApple的超类,我们就可以写入:
apples.add(new Apple());
apples.add(new FujiApple());
如果我们想往里面加入Apple的超类,编译器就会警告你:
apples.add(new Fruit());
apples.add(new Object());
因为我们不知道它是怎样的超类,所有这样的实例就不允许加入。

下面都还是正确使用的
fruits.add(new Fruit());
fruits.add(new Apple());
fruits.add(new FujiApple());
fruits.add(new Banana());


从这种形式的类型里获取数据又是怎么样的呢?
结果表明,你只能取出Object实例:因为我们不知道超类究竟是什么,编译器唯一能保证的只是它是个Object,因为Object是任何Java类型的超类。
Object object = apples.get(0);

package lucky678.generics;

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

/**
 * 通配符
 * 向下造型一个泛型对象的引用      ? super T
 * @author Jony
 *
 */
public class Gen6 {

	public static void main(String[] args) {
		List<Fruit> fruits = new ArrayList<Fruit>(); 
		List<? super Apple> apples= fruits; 
		
		apples.add(new Apple()); //正确
		apples.add(new FujiApple());//正确
		
		//apples.add(new Fruit()); //错误
		//apples.add(new Object());//错误
		
		//下面都还是正确使用的
		fruits.add(new Fruit());
		fruits.add(new Apple());
		fruits.add(new FujiApple());
		fruits.add(new Banana());
		
		//获取类型是不确定的父类型   编译器只能给确定最大父类型Object
		Object object = apples.get(0);
	}

}


总结 ? extends 和 the ? super 通配符的特征,我们可以得出以下结论:
(1)如果你想从一个数据类型里获取数据,使用 ? extends 通配符
(2)如果你想把对象写入一个数据结构里,使用 ? super 通配符
(3)如果你既想存,又想取,那就别用通配符。

猜你喜欢

转载自blog.csdn.net/xueli_jiao/article/details/11636105