Java必知必会之(二)---泛型(下)

上集讲到Java的泛型(Java必知必会之(二)---泛型(上))的原理和基本用法,本文将会讲到的是泛型的进阶知识:通配符、PECS原则和类型擦除。

1.类型通配符

类型通配符:额...说白了就是一个?。当确定集合是某种数据类型的时候,你可以写List,而当不确定集合是哪一种类型的时候,就可以写成List<?>。 当然,也可以用其它的字母来表示,如下常用的通配符有: E — Element,常用在java Collection里,如:List,Iterator,Set K,V — Key,Value,代表Map的键值对 N — Number,数字 T — Type,类型,如String,Integer等等 这些只是一些常用的约定写法,并不需要特别记住,我们可以随意用26位字母去表示。至于?和字母的区别就在于:在Java集合框架中,对于参数值是未知类型(即使用“?”通配符)的容器类,由于编译器无法预知其具体类型,所以只能读取,不能增删,但NULL是例外。 如下面的代码就会编译出错

List<?> list=new ArrayList<String>();
list.add("1");//error
list.add(NULL);//ok
复制代码

list定义的是无边界通配符,往一个未知类型的l中加入类型为String的数据,编译器就会报错,但加入null就不会报错。 通常,通配符有三种:

  1. 无边界通配符:<?> 就是?, 如 Set<?>、List<?>、Map<?,?>,作用是让泛型能接受未知类型的数据。
List<?> l=new ArrayList();
Set<?> s=new TreeSet();
复制代码
  1. 子类限定通配符:<? extends E> 表示能够接受指定类及其子类类型的数据。要求<?>的数据类型必须为E或者E的子类。
public static void c(List<? extends String> l){
     System.out.println(l.get(0));
}
public static void main(String[] args) {
		List<Integer> l1=new ArrayList();
		l1.add(1);
		
		List<String> l2=new ArrayList();
		l2.add("2");
		
		//error:The method c(List<? extends String>) in the type TestT is not applicable for the arguments (List<Integer>)
		TestT.c(l1);
		TestT.c(l2);
}
复制代码
  1. 超类限定通配符:<? super E> 表示能够接受指定类及其父类类型的数据.<?>必须为E或者E的父类。
public static void superD(List<? super Integer> s){
		System.out.println(s.get(0));
	}
List<String> lString=new ArrayList();
lString.add("2");
		
List<Object> lObject=new ArrayList();
lObject.add("2");
		
//error:The method superD(List<? super Integer>) in the type TestT is not applicable for the arguments (List<String>)
TestT.superD(lString);
TestT.superD(lObject);
复制代码

2.PECS原则

PECS全文为“Producer Extends, Consumer Super”,意思是作为作为生产者时使用extends,作为消费者时使用super。 啥玩意儿???听不懂 没关系 来看下面的例子:

List<? extends Ineteger> fe = new ArrayList();		
List<? super Ineteger> fs=new ArrayList();

//The method add(capture#5-of ? extends Object) in the type List<capture#5-of ? extends Object> is not applicable for the arguments (String)
//生产者操作
fe.add(1);
fs.add(1);
//消费者操作
for (Integer s : fe) {
}
for (Integer s : fs) {
}
复制代码

可以看出,fe.add()作为生产者的动作往集合中添加String类型的数据时,编译器会报错:因为java中不允许不确定的类型加入集合,作为Integer的子类,此时编译器并不知道fe被add进去的具体是什么子类型,因此就会报错(因为子类可能有着和父类不一样的形态,如果不限定规则,随便往里添加各种不同的子类,那在读取list里的内容时,就会出错,因为Java干脆把规则限定在前面)。而在进行消费者的操作时,fs在遍历数据时,由于数据类型为Integer或者Integer的父类,因此,<?>并不知道具体是哪一种类型,所以编译器会报错。因为PECS最大的原则就是无论是生产操作还是消费操作,必须让编译器知道具体操作的类型是什么,否则不好意思,不给过!! 由上面的例子我们可以整理出一下规则:

  1. 如果要从集合中读取类型T的数据,并且不能写入,可以使用 ? extends 通配符;(Producer Extends)
  2. 如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super 通配符;(Consumer Super)
  3. 如果既要存又要取,那么就不要使用任何通配符

3.类型擦除:

JDK1.5引入了泛型,旨在减少类型强制转换的操作,加强参数类型的安全,但是泛型的作用域在编译阶段,编译完成之后,所有泛型参数类型都会被清除,这就是类型擦除。 下面来看一段代码:

List<String> listString=new ArrayList();
List<Integer> listInteger=new ArrayList();
	
System.out.println(listString.getClass());
//输出:class java.util.ArrayList
	
System.out.println(listInteger.getClass());
//输出:class java.util.ArrayList
	
System.out.println(listInteger.equals(listString));
//输出:true
复制代码

上面的代码得出以下结论:

  1. listString、listInteger并不是ArrayList的子类,它们都是ArrayList
  2. listString、listInteger都指向同一个内存地址

由此可见,泛型的存在只是在编译阶段,在运行阶段它们的类型已经被擦除,而泛型也不存在父类子类这种说法。

觉得本文对你有帮助?请分享给更多人

关注「编程无界」,提升装逼技能

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值