数据结构与集合(五) --- 集合与泛型

为什么在集合中用泛型

泛型与集合的联合使用,可以把泛型的功能发挥到极致.

  • List、List、List 三者的区别?
  • 怎么区分 的使用场景?
    List 完全没有类型限制和赋值限定, 如果天马行空地乱用,迟早会遭遇类型转换失败的异常. 很多程序员觉得List 的用法完全等同于List, 但在接受其他泛型赋值时会编译出错. List 是一个泛型,在没有赋值之前,表示它可以接受任何类型的集合赋值,赋值之后就不能随便往里面添加元素了.
    下方的例子说明了三者的区别,以List为原型展开说明:
public class ListNoGeneric {
     
    public static void main(String[] args) {
     
        // 第一段: 泛型出现之前的集合定义方式
        List a1 = new ArrayList();
        a1.add(new Object());
        a1.add(new Integer(111));
        a1.add(new String("hello a1"));
        
        // 第二阶段: 把a1引用赋值给a2,注意a2与a1的区别是增加了泛型限制
        List<Object> a2 = a1;
        a2.add(new Object());
        a2.add(new Integer(222));
        a2.add(new String("hello a2"));
        
        // 第三段: 把a1 引用赋值给a3,注意a3与a1的区别是增加了泛型
        List<Integer> a3 = a1;
        a3.add(new Integer(333));
        // 下方两行编译错误,不允许增加费Integer类型进入集合
//        a3.add(new Object());
//        a3.add(new String("hello a3"));
        
        // 第四段: 把a1引用赋值给a4,a1与a4 的区别是增加了通配符
        List<?> a4 = a1;
        // 允许删除和清除元素
        a1.remove(0);
        a4.clear();
        // 编译出错,不允许增加任何元素
//        a4.add(new Object());
    }
}
 
  

第一段说明: 在定义List之后,毫不犹豫的往集合里装入三种不同的对象: Object, Integer和String, 遍历没有问题,但是贸然以为里面的元素都是Integer,使用强制转换,则抛出 ClassCastException 异常.
第二段说明: 把a1 赋值给a2, a2 是List 类型的,也可以再往里装入三种不同的对象. 但是如果在第三段中 List a3 = a2;是会出现编译错误,所有List和List 是不完全相同的.
第三段说明: 由于JDK5之后才出现泛型,考虑到向前兼容,因此历史代码有时需要赋值给新泛型代码,从编译器角度是允许的.
下面是一段问题代码:

JSONObject jsonObject = JSONObject.formObject("{\"level\":[\"3\"]}");
List<Integer> intList = new ArrayList<>(10);
if(jsonObject != null){
     
	intList.addAll(jsonObject.getJSONArray("level"));
	int amount = 0;
	for(Integer t : intList){
     
		// 抛出 ClassCaseException 异常 : String cannot be cast to Integer
		if(condition){
     
			amount = amount + t;
		}
	}
}

addAll 的定义如下:

public boolean addAll(Collection<? extends E> c){
     ...}

进行了泛型限制,示例中addAll 的实际参数时 getJSONArray 返回的 JSONArray 对象,它并非为List,更加不是Integer 集合的子类,但为何编译不报错?查看JSONArray 的定义:

pubic final class JSONArray extends AbstractJSON implements JSON,List{
     }

JSONArray 实现了List, 是非泛型集合,可以赋值给任何泛型限制的集合. 编译可以通过,但在运行时报错,这个一个隐藏得很深的Bug,最终导致发生线上故障. 在JDK5之后,应尽量使用泛型定义, 以及使用类, 集合, 参数等.
List 赋值给 List 是不允许的,若反过来赋值:

  List<Integer> intList = new ArrayList<>(3);
        intList.add(111);
        List<Object> objects = intList;

事实上,依然会编译错误.
注意: 数组可以这样赋值,因为它是协变的,而集合不是.

第四段说明: 问号在正则表达式可以匹配任何字符,List 称为通配符集合. 它可以接受任何类型的集合引用赋值,不能添加任何元素, 但是可以remove 和 clear 操作, 并非 immutable 集合. List 一般作为参数来接收外部的集合,或者返回一个不知道具体类型的集合.
List 最大的问题是只能放置一种类型,如果随意转换类型的话,就是破窗理论, 泛型就失去了类型的安全意义.

****两种语法:
是Get First , 适用于,消费集合元素为主的场景; 是 Put First , 适用于,生产集合元素为主的场景.

可以赋值给任何T 及 T **子类的集合, 上届为 T**, 取出来的类型带有泛型限制,向上强制转型为 T . null 可以表示任何类型,所以除了null 外,任何元素都不得添加进 集合内. 可以赋值给任何 T 及 T **父类集合, 下届为T**. extends 的场景是put 功能受限, 而 super 的场景是 get 功能受限. 下例中,以加菲猫, 猫, 动物为例, 说明 extends和super 的详细语法差异: ```java /** * 用动物的猫科与加菲猫的继承关系说明 extends 与 super 在集合中的意义 */ public class AnimalCatGarfield { public static void main(String[] args) { // 第1端.声明三个依次继承的类的集合: Object > 动物 > 猫 > 加菲猫 List animal = new ArrayList<>(); List cats = new ArrayList(); List garfields = new ArrayList(); animal.add(new Animal()); cats.add(new Cat()); garfields.add(new Garfield()); // 第2端. 测试赋值操作 // List extendsCats = animal; // 编译错误, 只能赋值Cat 或 Cat的子类的集合 List superCats = animal; // 可以赋值Cat 或 Cat的父类集合 List extendsCatFormCats = cats; List superCatFormCats = cats; List extendsCatFormGarfield = garfields; // List superCatFromGarfield = garfields; // 编译错误, 只能赋值给 Cat 或 Cat的父类的集合 // 第3端. 测试add方法 // 下面三个方法中的所有的 都无法进行add操作,编译均出错 // extendsCatFormCats.add(new Animal()); // extendsCatFormCats.add(new Cat()); // extendsCatFormCats.add(new Garfield()); // superCatFormCats.add(new Animal()); 编译错误, 只能添加 Cat 或 Cat 子类的集合 superCatFormCats.add(new Cat()); superCatFormCats.add(new Garfield()); // 第4端. 测试get 方法 // 所有的super 操作能够返回元素,但是泛型丢失,只能返回Object 对象 // 以下extends 操作能够返回元素 Object catExtendsObject = extendsCatFormCats.get(0); Cat catExtendsCat = extendsCatFormCats.get(0); // Garfield garfield = extendsCatFormGarfield.get(0); 编译错误, 虽然Cat 集合从Garfield 赋值而来,但类型檫除后,是不知道的 } } ``` 第1端说明: 声明三个泛型集合, 可以理解为三个不同的笼子, List 住的是动物, List 住的是猫(猫科动物) , List 住的是加菲猫. Garfield 继承于 Cat, 而 Cat 继承自 Animal. 第2端说明: 以Cat 类为核心,因为它有父类也有子类. 定义类型限定集合, 分别为 List 和 List . 把List 对象赋值给两者都是可以的, 但是把List 赋值给 List 时就会编译错误, 因为赋值给 的类型, 只有Cat 自己和它的子类集合. 尽管它是类型安全的, 但依然有泛型信息, 因而从笼子里取出来的必然是只猫,而List 里面可能住着 毒蛇, 鳄鱼等其他动物. 把List 赋值给List 时, 也会编译报错,因为能赋值给 的类型的,只有 Cat 自己和它的父类. 第3端说明, 所有的List 都会编译错误, 无法进行add 操作,这是因为除了null 外,任何元素都不能被添加进 集合内. List 可以往里增加元素,但是只能添加Cat 自身及子类对象,假如放入一块石头,则明显违背了Animal 大类的性质. 第4端说明: 所有的List 集合可以执行get操作,虽然能够返回元素,但是类型丢失,即只能返回Object 对象,List 可以返回带类型的元素,但只能返回Cat 自身及其父类对象,因为子类类型被檫除了. 对于一个笼子, 如果只是不断地向外取动物而不向里放的话,则属于Get First, 应采用 ; 相反,如果经常向里放动物的话,则应采用 ,属于Put First