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

为什么在集合中用泛型

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

  • List、List、List<?> 三者的区别?
  • 怎么区分<? extends T> 与 <? super T> 的使用场景?
    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的区别是增加了泛型限制<Object>
        List<Object> a2 = a1;
        a2.add(new Object());
        a2.add(new Integer(222));
        a2.add(new String("hello a2"));
        
        // 第三段: 把a1 引用赋值给a3,注意a3与a1的区别是增加了泛型<Integer>
        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<Integer> 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 最大的问题是只能放置一种类型,如果随意转换类型的话,就是破窗理论, 泛型就失去了类型的安全意义.

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

<? extends T> 可以赋值给任何T 及 T **子类的集合, 上届为 T**, 取出来的类型带有泛型限制,向上强制转型为 T . null 可以表示任何类型,所以除了null 外,任何元素都不得添加进<? extends T> 集合内. <? super T> 可以赋值给任何 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<? extends Cat> extendsCats = animal; // 编译错误, 只能赋值Cat 或 Cat的子类的集合 List<? super Cat> superCats = animal; // 可以赋值Cat 或 Cat的父类集合 List<? extends Cat> extendsCatFormCats = cats; List<? super Cat> superCatFormCats = cats; List<? extends Cat> extendsCatFormGarfield = garfields; // List<? super Cat> superCatFromGarfield = garfields; // 编译错误, 只能赋值给 Cat 或 Cat的父类的集合 // 第3端. 测试add方法 // 下面三个方法中的所有的<? extends T> 都无法进行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<? extends Cat> 和 List<? super Cat> . 把List 对象赋值给两者都是可以的, 但是把List 赋值给 List<? extends Cat> 时就会编译错误, 因为赋值给<? extends Cat> 的类型, 只有Cat 自己和它的子类集合. 尽管它是类型安全的, 但依然有泛型信息, 因而从笼子里取出来的必然是只猫,而List 里面可能住着 毒蛇, 鳄鱼等其他动物. 把List 赋值给List<? super Cat> 时, 也会编译报错,因为能赋值给<? super Cat> 的类型的,只有 Cat 自己和它的父类. 第3端说明, 所有的List<? extends T> 都会编译错误, 无法进行add 操作,这是因为除了null 外,任何元素都不能被添加进<? extends T> 集合内. List<? super Cat> 可以往里增加元素,但是只能添加Cat 自身及子类对象,假如放入一块石头,则明显违背了Animal 大类的性质. 第4端说明: 所有的List<? super T> 集合可以执行get操作,虽然能够返回元素,但是类型丢失,即只能返回Object 对象,List<? extends Cat> 可以返回带类型的元素,但只能返回Cat 自身及其父类对象,因为子类类型被檫除了. 对于一个笼子, 如果只是不断地向外取动物而不向里放的话,则属于Get First, 应采用<? extends T> ; 相反,如果经常向里放动物的话,则应采用<? super T> ,属于Put First
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值