Hello,今天给各位童鞋们分享Java泛型,赶紧拿出小本子记下来吧!
一、泛型的定义
泛型是jdk1.5出现的新特性,它实现了参数化类型,使代码能够应用于多种类型,让类或方法具备广泛的表达能力
二、泛型的好处
1.参数化类型:可以创建按类型进行参数化的类,使代码更加通用灵活,提高了代码的重用率。集合很好的体现了这一好处,比如ArrayList,使用new
ArrayList可以创建String类型的集合;使用new
ArrayList可以创建Integer类型的集合,而不用为String和Integer创建两种不同类型的集合类
2 .类型安全:上面第一点并不是泛型被设计出来的主要目的,因为就第一点而言,泛型能做的,Object也能做到,事实也是如此,在jdk1.5之前,集合类就是用Object实现的,所有的类型都可以扔进Object数组里存储:
List list = new ArrayList();
list.add("abc");
list.add(1);
int size = list.size();
for (int i = 0; i < size; i++) {
Integer value = (Integer) list.get(i); //ClassCastException
System.out.println(value);
}
运行以上代码,便会抛出一个异常:java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer,而使用了泛型,可以在编译期就能发现这个在运行期才会发现的错误:
List list = new ArrayList();
list.add(1);
list.add("abc"); //编译器提示错误
在定义List时,便指定了该集合只能存储Integer类型的数据,所以在使用add方法时,编译器便会明确地提示String类型的数据不能被存储到Integer类型的集合中,所以泛型最主要的好处便是类型安全,在运行期才能发现的错误,提前到编译器就能发现,提高了代码的安全性(平时在编码中需要养成习惯,使用集合类时,在定义时就要明确集合存储的类型)
3.消除类型强制转换:由于在定义集合类时已经明确了是某种类型的集合,所以在取集合数据时,可以不用像第一个代码例子一样,强转类型,提高了代码的可读性。
List list = new ArrayList();
list.add(2);
list.add(1);
int size = list.size();
for (int i = 0; i < size; i++) {
Integer value = list.get(i);
System.out.println(value);
}
三、泛型的使用
1.泛型类
在类中引入类型变量,用尖括号括起来,并放在类名的后面,可以有多个类型变量,用逗号分隔
- 类型参数只能是类类型,不能是基本数据类型
可以往下看到泛型擦除,这个Template泛型类,运行时会经历类型擦除,类型变量T就变成了Object,当调用get/set方法时,虚拟机会将其自动进行强转成具体的数据类型,由于Object无法强转成基础数据类型,所以泛型参数不能是基本数据类型
Template templateInt = new Template(); //编译器提示error
运行时类型查询只适用于原始类型
if(templateStr instanceof Template){ //提示error
}
System.out.println(templateObj.getClass() == templateStr.getClass()); //打印true
System.out.println(templateStr.getClass() == templateInte.getClass()); //打印true
2.泛型方法
类型变量放在修饰符的后面,返回类型的前面,泛型方法可以定义在普通类中,也可以定义在泛型类中
修饰符 <T,E...> 返回值类型 方法名(形参列表){
方法体...
}
3.泛型接口
interface 接口名称<T,E...>{
...
}
四、泛型通配符
1.通配符
如上,我们已经把西瓜和苹果都放进了盘子里,如果有更多种类的水果,是不是还要新建更多的setFruit方法呢,我们可以想到,将方法参数换成Plate fruitPlate,而不是一个具体的水果类(苹果、西瓜),但是虽然Apple和Watermelon都是Fruit的子类,Plate、Plate和Plate没有任何关系,是不能进行赋值的,这里我们可以使用通配符"?"
private void setFruit(Plate<?> fruitPlate){
Fruit fruit = (Fruit) fruitPlate.getT();
System.out.println(fruit);
}
Plate applePlate = new Plate<>(new Apple());
setFruit(applePlate);
Plate watermelonPlate = new Plate<>(new Watermelon());
setFruit(watermelonPlate);
Plate和Plate< Object>的功能类似,可以接受任意类型的参数,但是如果要把上面代码中的Plate换成Plate< Object>是会报错的,因为Plate和Plate并无任何关系,不存在父子类的关系,而Plate<?>是所有Plate的父类,所以可以将Plate和Plate传进来
2.?与T
?表示的是多种类型,而T只能表示具体的某一种类型
?可以理解为类型实参,而T表示的是类型形参
在编写代码时,不能用?作为一种类型,如:? t = xxx
3.Plate<?>与Plate
Plate<?> plate = new Plate(new Apple());
Object apple = plate.getT();
//编译器报错
plate.setT(new Apple());
五、泛型的边界
- 上界
上面装水果的例子,最终是采用了Plate<?>当方法参数,实际上是有很大的问题的,因为他可以接受任意类型的参数传递进来,如果传进来的参数不是水果类,在强转时,是会报错的,那么有没有可能限制传进来的参数只能是水果类呢
private void setFruit(Plate<? extends Fruit> fruitPlate){
Fruit fruit = fruitPlate.getT();
System.out.println(fruit);
}
Plate applePlate = new Plate<>(new Apple());
setFruit(applePlate);
Plate watermelonPlate = new Plate<>(new Watermelon());
setFruit(watermelonPlate);
使用extends关键字表明fruitPlate的类型只能是Fruit或者Fruit的子类
上界的局限性:只能取不能存
//编译器报错
fruitPlate.setT(new Apple());
由于fruitPlate表明类型只能是Fruit或者Fruit的派生类,可能是Apple类,也有可能RedApple类,具体是哪个派生类不清楚,由于粒度无法确定,所以是不能存储的,但是可以取元素,由于存储的元素是Fruit或者Fruit的派生类,所以取出来的元素都可以指向Fruit的引用,即:
Fruit fruit = fruitPlate.getT();
2.下界
private void setFruit5(Plate<? super Apple> applePlate){
Object fruit = applePlate.getT();
System.out.println(fruit);
applePlate.setT(new Apple());
}
使用super关键字表明类型只能是Apple或者Apple的基类
下界的局限性:可以存,取元素时只能赋值为Object类型
下界规定了元素类型的下限,实际上是放松了容器元素的类型控制。既然元素是Fruit的基类,那往里存粒度比Fruit小的都可以。但是在取元素时,只能将取出的元素指向Object.
六、泛型的继承
若定义了一个父类:Father,一个子类Son
TeamLate< S>和TempLate< T>没有任何联系,他们都是原始类型TempLate的一个子类型,所以参数化类型可以转换为一个原始类型
泛型类/接口可以被继承/实现
若继承的子类是一个普通类,则在继承时,需指定父类泛型类的类型,若不指定,则默认为Object类型
class TemplateSon extends Template{
}
class TemplateSon2 extends Template{
}
若继承的子类也是一个泛型类,则在继承时,指定的类型变量需要包含父类的,不然在new 子类时,无法得知父类具体是什么类型
class TemplateSon<T, E> extends Template{
}
//报错
class TemplateSon2 extends Template{
}
七、泛型的擦除
泛型代码只存在编译阶段,在运行期间,与泛型相关的代码就会被擦除,擦除类型变量,替换为限定类型,无限定的变量用Object(如果有多个类型变量,则用第一个限定的类型变量替换)
- 无限定类型擦除,替换为Object,以上Plate被擦除后的代码如下,可用反射或者反编译软件验证
- 有限定类型擦除
- 桥方法
好啦,今天的文章就到这里,希望能帮助到屏幕前迷茫的你们!