Java 泛型和类型擦除
泛型抽离了数据类型与代码逻辑,提高程序代码的简洁性和可读性,并提供可能的编译时类型转换安全检测功能。
1.泛型
泛型,广泛适用的类型
,是JDK5引入的新特性,官方的说法是为了 参数化类型,就是将类型作为一个参数传递给一个方法或者传递给一个类。当具体的类型确定后,泛型又提供了一种类型检测机制,只有类型匹配的数据才能正常传递。
1.1 泛型的定义和使用
泛型按照使用情况分为3种:泛型类
、泛型方法
、泛型接口
。
Java建议我们用单个大写字母来代替类型参数,常见的如:
字母 描述 T 一般的任何类 E Element/Exception K Key V Value,与K配合使用 S Subtype,类型擦除
泛型类和泛型接口
//1.泛型类:单个参数
public class singleType<T> {...}//T就是类型的参数,等待传递节来
//2.泛型类:多个参数
public class MultiType<K , V> {...}
//3.泛型接口
public interface Iterble<T>{...}
泛型方法
//1.泛型方法
public class Test {
//1.无返回值的泛型方法
public <T> void testMethod1(T t){...}
//2.有返回值的泛型方法 <T>中的T表示类型参数 方法中的T表示参数化类型
public <T> T testMethod2(T t){...}
}
//2.泛型类+泛型方法(同名)
public class Test<T> {
public <T> T testMethod(T t){...}
}//此例 方法中的T与Test<T>中的T没有任何联系,为了方便区分,建议不使用同名
//3.泛型类+泛型方法(不同名)
public class Test<T> {
public <E> E testMethod(E t){...}
}//此例 方法中的T与Test<T>中的T没有任何联系,为了方便区分,建议不使用同名
//泛型方法始终以自己定义的类型参数为准
为什么泛型方法是
public <T> T testMethod(T t)
这样,而不能是public T testMethod(T t)
这样呢?因为<T>
表示声明
类型参数为T,也就是声明后面方法括号(T t)
中T
的类型,想象一下如果直接传递参数不能确定到底是接收哪一种类型的参数是不可取的。
1.2 通配符 ?
除了<T>
还有<?>
表示泛型,?
被称为通配符,通配符的出现就是为了指定泛型中的类型范围。它分为:<?>
无限定通配符、<? extends T>
有上限的通配符、<? super T>
有下限的通配符。
无限定通配符<?>
<?>
常与容器类
配合使用,它其中的 ?
代表的是未知类型,所以涉及到 ?
时的操作,一定与具体类型无关。<?>
提供了只读功能,它不关心元素数量(就是是ArrayList<?>
也不能插入数据)
<? extends T>
?
代表着类型未知,但是我们的确需要对于类型的描述再精确一点,我们希望在一个范围内确定类别,比如类型 T 及类型 T 的子类都可以。
1.3 泛型中值得注意的地方
泛型不接受8种基本数据类型,它们是:byte,short,char,int,long,float,double,boolean
,需要使用它们的包装类。
2.类型擦除
在介绍类型擦除前先看一个栗子:
System.out.println( new ArrayList<Integer>().getClass() == new ArrayList<String>().getClass() );//输出true
对于熟悉和不熟悉的小伙伴立马就能给出答案,那么为什么是true呢,这就是
类型擦除
的作用。在JVM中都表示为List.class,而不是List<Integet>.class。
由于泛型信息只存在与代码编译阶段,在进入JVM之前,与泛型相关的信息
会被擦除掉,即类型擦除
. 并且在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限(<?>
),如则会被转译成普通的 Object 类型,如果指定了上限(<? extends Sting>
)则类型参数就被替换成类型上限String
2.1 类型擦除带来的局限性
类型擦除,是泛型能够与之前的 Java 版本代码兼容共存的原因。但也因为类型擦除,它会抹掉很多继承相关的特性,这是它带来的局限性。理解类型擦除有利于我们绕过开发当中可能遇到的雷区,同样也能让我们绕过泛型本身的一些限制。比如:
正常情况下因为泛型的限制,因为类型不匹配,最后一行代码会不编译通过,但是基于对类型擦除的理解(即在JVM中Integer
类型变为了Object
),就可以利用反射原理绕过 Java编译检测 的限制,直接使用反射技术拿到这个List<Integer>类对象,当然,在JVM中他已经是List<Object>了,之后调用add
方法:
public class Main {
public static void main(String[] args) throws Exception {
List<Integer> list = new ArrayList<>();
list.add(1);
//list.add("abc");
Method method = list.getClass().getDeclaredMethod("add",Object.class);
// 存入string字符串
method.invoke(list,"abc");
for(Object obj : list){
System.out.println(obj);
}
}
}
结果:
1
abc
可以看见利用类型擦除原理,用反射技术的手段就完美绕开了正常开发中编译器不允许的类型不配匹的限制。