牛皮!阿里大佬分享的Java泛型全解笔记火了,完整版实在太香了

492 阅读5分钟

Hello,今天给各位童鞋们分享Java泛型,赶紧拿出小本子记下来吧!

image.png

一、泛型的定义

泛型是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.泛型类

在类中引入类型变量,用尖括号括起来,并放在类名的后面,可以有多个类型变量,用逗号分隔

image.png

  • 类型参数只能是类类型,不能是基本数据类型

可以往下看到泛型擦除,这个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...> 返回值类型 方法名(形参列表){

方法体...

}

image.png 3.泛型接口

interface 接口名称<T,E...>{

...

}

四、泛型通配符

1.通配符

image.png 如上,我们已经把西瓜和苹果都放进了盘子里,如果有更多种类的水果,是不是还要新建更多的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());

五、泛型的边界

  1. 上界

上面装水果的例子,最终是采用了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

image.png TeamLate< S>和TempLate< T>没有任何联系,他们都是原始类型TempLate的一个子类型,所以参数化类型可以转换为一个原始类型

image.png 泛型类/接口可以被继承/实现

若继承的子类是一个普通类,则在继承时,需指定父类泛型类的类型,若不指定,则默认为Object类型

class TemplateSon extends Template{

}

class TemplateSon2 extends Template{

}

若继承的子类也是一个泛型类,则在继承时,指定的类型变量需要包含父类的,不然在new 子类时,无法得知父类具体是什么类型

class TemplateSon<T, E> extends Template{

}

//报错

class TemplateSon2 extends Template{

}

七、泛型的擦除

泛型代码只存在编译阶段,在运行期间,与泛型相关的代码就会被擦除,擦除类型变量,替换为限定类型,无限定的变量用Object(如果有多个类型变量,则用第一个限定的类型变量替换)

  • 无限定类型擦除,替换为Object,以上Plate被擦除后的代码如下,可用反射或者反编译软件验证

image.png

  • 有限定类型擦除

image.png

  • 桥方法

image.png 好啦,今天的文章就到这里,希望能帮助到屏幕前迷茫的你们!