泛型结🐕
你是否还不知道为什么要引入泛型?
你是否还不知道怎么使用泛型?
你是否还不知道类型擦除?
你是否还不知道怎么使用通配符?
那么读上几分钟,让你的人生不再遗憾?哈撒给😁
前言
泛型是JDK1.5引入的新特性,泛型提供了编译时类型检测安全机制。
可能不是太好理解,下面使用代码演示
没错,当你定义了泛型为Integer类型之后,试图向集合中加入String类型,那编译肯定不会通过。
至于怎么向Integer集合类型中加入String类型,那么就肯定要绕过编译了!
然后你就可以想到使用什么了,没错,就是反射!
一、使用反射绕过编译时的类型检查
List<Integer> list = new ArrayList<>();
list.add(2);
String str = "a";
// list.add(str);
//实例化对象
Class<? extends List> clazz = list.getClass();
//获取add类,因为反射会将类的属性、构造函数、方法等成为一个个的对象
Method add = clazz.getDeclaredMethod("add", Object.class);
//执行invoke方法,向集合中添加数据
add.invoke(list,str);
System.out.println(list);
至于原理我已经在反射一文讲清楚了。该文是讲泛型的,反射就不多bb了。
二、为什么要引入泛型的呢?
在JDK1.5之前,如果想要实现一个通用的方法,那么就需要使用Object
/**
* @author:抱着鱼睡觉的喵喵
* @date:2021/4/1
* @description:
*/
public class Common {
private Object[] data;
public Common(int length) {
this.data = new Object[length];
}
public Object getData(int index) {
return data[index];
}
public void addData(int index, Object data) {
this.data[index] = data;
}
}
然后就使用呗
Common common = new Common(5);
common.addData(0,"zsh");
common.addData(1,23);
String data = (String) common.getData(0);
String data2 = (String) common.getData(1);
System.out.println(data);
System.out.println(data2);
从这可以看出使用Object可以实现方法的通用,但是有个很大的弊端
当我向数组中加入整型和字符串时,在获取数据时,转换为String类型失败,当然都能明白这个应该转换为Integer类型
但是如果数据量较大时,是不是很难避免类型转换异常
所以为了代码得到重(chong)用等等一系列的原因,泛型便出现了
三、泛型的使用方式
1.泛型类
public class Generic<T> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
public class GenericExtends<T> extends Generic<T> {
}
class GenericExtends2<T> extends Generic<String> {
}
2.泛型方法
public class Generic<T> {
public <E> void iterator(E[] arr) {
for (E ele : arr) {
System.out.printf("%s\t",ele);
}
}
}
Generic generic = new Generic();
Integer arr[] = {1,35,56};
String[] str ={"hello"};
generic.iterator(arr);
generic.iterator(str);
3.泛型接口
public interface Alorgithm<T>{
T method();
}
四、通配符的使用
我们经常会遇见这几种通配符,如T E K V ?,那么它们都是什么意思呢?
其实这几个字符是约定俗成的,你可以使用A-Z的任何一个字符,但这样的话,不太容易理解语义。
T:表示一个Java类型
?: 表示不确定的 java 类型
K V : java键值中的Key Value
E :代表Element
或许不是很好理解,那么看完下面的例子你就明白了
1.通配符上限<? extends T>
现在我自定义四个类,分别是Car、BusCar、SportCar、Airport
其中BusCar和SportCar继承自Car,Airport与其它几个类无任何关系
public class Car {
public String toString() {
return "car ---> run build";
}
}
public class BusCar extends Car{
@Override
public String toString() {
return "busCar ----> run build";
}
}
public class SportCar extends Car{
@Override
public String toString() {
return "sportCar -----> run build";
}
}
public class Airport {
public String toString() {
return "airPort ---> run build";
}
}
现在我要对Car类型的子类型车做出统一操作
public static void start(ArrayList<Car> cars) {
for (Object car : cars) {
System.out.println(car);
}
}
public static void main(String[] args) {
ArrayList<BusCar> list = new ArrayList<>();
BusCar busCar = new BusCar();
BusCar busCar2 = new BusCar();
list.add(busCar);
list.add(busCar2);
start(list);
}
发现ArrayList<BusCar>向ArrayList< Car>类型转换失败了,那这是为什么呢? 我们都知道BusCar是直接可以转换为Car类的;子类->父类
其实这也很简单,ArrayList<BusCar>和ArrayList< Car>是不同的类型,我们都知道泛型都有安全类型检测;假如说上面的可以转换,那岂不是我也可以向其中添加SportsCar了,甚至Car本身都能添加,这样就乱套了。(本来是一群BusCar,突然来了一个SportsCar,这个SportsCar还不被群殴死呀)
重点来了
所以可以使用?
再次运行
这样以来也可以保证类型的安全,如果在这种情况下加入其他类型的就会报错。
虽然可以这样使用,但是如果我直接向其中只加入Airport类会怎么样呢(Airport不是Car的子类)
这样以来是不是有很大的问题,本来start方法只是用来只执行Car类或者Car子类的;突然跑来一个无关的Airport(飞机)类,而且还执行成功了,是不是很荒唐,所以通配符上限就来了。
<? extends T> 表名该类型必须是T类或者T的子类,T就代表向上的极限值。
如果再使用与Car无关的类,就会报错;
2.下界通配符 < ? super E>
表示该类型必须是E本身或者E的父类(超类),上限是Object
3. T 和 ? 的区别
1.有时候我们可以对 T 进行操作,但是对 ?却 不行,比如如下这种
T car = start(); //正确
? car = start(); //错误
T表示一个Java类型
?表示一个不确定的Java类型
2.T可以多重限定而?不行
这个T必须是Auth和Alorgithm的共同子类;?不可以的原因是?表示的是一个不确定的类型;
public class Alu implements Alorgithm,Auth{
//这个T必须是Auth和Alorgithm的共同子类
private static<T extends Auth & Alorgithm> void test(T t) {
}
//接口方法
@Override
public Object method() {
return null;
}
}
3.通配符?可以使用超类限定而T不可以
T extends A
? extends A
? super A
五、类型擦除
List<Integer> list = new ArrayList<>();
list.add(2);
String str = "a";
list.add(str); //报错
System.out.println(list);
这样添加一个整型之后,再添加一个字符串,肯定报错;因为泛型在编译期间会进行类型安全检测
但是下面这样就可以
List<Integer> list = new ArrayList<>();
list.add(2);
String str = "a";
//实例化对象
Class<? extends List> clazz = list.getClass();
//获取add对象,因为反射会把属性,构造函数,方法等分为一个个的对象
Method add = clazz.getDeclaredMethod("add", Object.class);
//执行invoke操作,也就是添加str到list中
add.invoke(list,str);
System.out.println(list);
使用反射绕过了编译,其实Java的泛型是伪泛型,意思是在编译期间,所有的泛型信息都会被擦除;比如上面我定义的泛型是List< Integer >类型,但是到了编译过后就变成了List,JVM只看到了List,看不到任何泛型的信息;
所以在编译器要尽可能的发现错误;但仍无法避免类型转换异常