什么是泛型?为什么需要泛型?
泛型的定义
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型
泛型的场景意义
也就是说泛型是一个提供编译器提前检测的机制,可以从一个例子看出
public static void main(String[] args){
List list = new ArrayList();
list.add("1");
list.add(2);
for(int i = 0; i< arrayList.size();i++){
String item = (String)arrayList.get(i);
}
}
在执行过后,会抛出异常
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
缺少了一个检测的机制,就需要我们手动去检测是否是当前类型的?改写代码为如下:
public static void main(String[] args) {
List list = new ArrayList();
list.add("1");
list.add(2);
for(int i = 0; i< list.size();i++){
if(list.get(i) instanceof String){
String item = (String)list.get(i);
}
else if(list.get(i) instanceof Integer){
Integer item = (Integer)list.get(i);
}
}
}
此时代码可以正常运行了,但是这混乱的列表中,各种各样的类型都有可能,不好使用,每次都要进行判断是何种类型,才能够正常使用,因此引入了泛型,可以规范指定类型的列表只能传入当前类型
List<String> list = new ArrayList<>();
list.add("1");
list.add(2);// 此时,如果再按以上的输入,就能够看到不符合当前的类型String
泛型的优点
(1)保证了类型的安全性
上述说到的场景其实就是对于安全性的检测,当放入错误的类型的时候,编译器会发现类型不匹配,终止编译,编译失败
List<String> list = new ArrayList<>();
list.add(2);// 编译失败
(2)消除强制类型转换
还是上述场景中说到的情况1,就是当不知道一个类型的时候,都属于Object,需要转换为我们想要的类型
Integer a = (Integer) b;
在使用泛型了之后,就能够确定类型,使得代码减少了强制类型转换的步骤,代码可读性也提高了
(3)避免了不必要的拆箱装箱
非泛型编程,消耗资源在装箱拆箱,集合的时候体现的十分明显
Object b = 1; // 装箱
int a = (int) b; // 拆箱
泛型编程
public static T getValue<T>(T b){
return b;
}
public static void main(String[] args){
int a = getValue<int>(1); // 指定了泛型类型,也就不会进行拆箱装箱操作了
}
(4)提高代码的重用性
这点可以从集合中看出,比如分别需要只适用于 String 和 只适用 Integer 的列表,当没有泛型的时候,就会有两个实现类
class a{
String ...
}
class b{
Integer ...
}
当引入泛型后,就更加可以实现代码的复用,实际上两者的代码都是相似的
class a<E>{
E ...
}
泛型中占位符 K T V E
在Java已有的类源码中会经常见到类上有泛型
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
...
}
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
...
}
这些参数其实都是一样的,都仅仅是一个占位符,但为了更高的区别其意义,有了不同的泛型参数名称,常用的如下:
E:Element(在集合中使用,因为集合中存放的是元素)
T:Type(Java类)
K:Key(键)
V:Value(值)
N:Number(数值类型)
?:表示不确定的 java 类型
如何使用泛型
使用方法
使用方式有三种:
- 泛型类
可以有多个饭女性类型public class 类名 <泛型类型1, 泛型类型2, ...>
- 泛型接口
public <泛型类型> 返回类型 方法名(泛型类型 变量名);
- 泛型方法
public <泛型类型> 返回类型 方法名(泛型类型 变量名){ }
public 与 返回值中间非常重要,可以理解为声明此方法为泛型方法。
只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
错误写法:报错,会提示需要引入类T
public T get(T t){
}
正确写法:正常,此时的 T 仅仅只是标识位,没有任何意义
public <T> T get(T t){
}
使用场景
对于项目开发的过程中,常使用于统一返回接口当中,以统一返回结果举例泛型的使用
/**
* @description:
* @author: HWH
* @create: 2022-10-17 14:56
**/
public class Result<T>{
private String code;
private String msg;
private T data;
public Result(String code, String msg, T data){
this.code = code;
this.msg = msg;
this.data = data;
}
public static <T> Result success(T data){
return new Result<>("500", "操作成功", data);
}
/.. 省略 getSet方法 ../
}
泛型通配符
用于解决泛型之间引用传递问题的特殊语法,有以下三类
- 无边界的通配符:<?>
- 接收未知类型的数据
- 固定上边界的通配符:<? extends E>
- 固定上边界的通配符的泛型,能够接受指定类及其子类性的数据
- 固定下边界的通配符:<? super E>
- 固定下边界,能够接受指定类及其父类类型的数据
泛型的实现原理
JVM实现了泛型擦除机制,在编译的期间,擦除反省语法,并做出类型转换
public class A<T>{
private T value;
}
在编译器运行后,进行泛型擦除,替换成 Objcet
public class A{
private Objcet value;
}
一般来说,泛型擦除后由于不知道类型,都替换为所有引用类型的父类Objcet
在使用泛型通配符后,就有可能不是替换成Object,比如
public class B<T extends String>{
private T value;
}
在擦除后
public class B{
private String value;
}
由于类型都被擦除成了 Objcet,当我们需要使用里面的内容的时候,还是需要强制类型转换的,但此时不需要我们操作,编译器会自动帮我们添加上相应的转换
引用上面的一段代码
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
for(int i = 0; i< list.size();i++){
String item = list.get(i);
}
}
在反编译后
public static void main(String[] var0) {
ArrayList var1 = new ArrayList(); // 这里的String已经被类型擦除
var1.add("1");
var1.add("2");
for(int var2 = 0; var2 < var1.size(); ++var2) {
String var3 = (String)var1.get(var2); // 这里是编译器帮我们加上的强制类型转换代码
}
}
在字节码中体现为checkcast
进行强制类型转换
所以说,编译器其实是没有泛型这个概念的,会将所有的类型进行擦除,然后进行强制类型转换。
泛型的作用就是指定要转换成什么样的类型,以及作为一个安全检测机制的作用,如果不符合类型,不能够通过编译
资料来源
java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一
Java 泛型 | 菜鸟教程
Java泛型详解,史上最全图文详解
泛型的基本原理