谈谈泛型和泛型擦除
所谓泛型,就是指在定义一个类、接口或者方法时可以指定类型参数。这个类型参数我们可以在使用类、接口或者方法时动态指定。
使用泛型可以给我们带来如下的好处:
- 编译时类型检查:当我们使用泛型时,加入向容器中存入非特定对象在编译阶段就会报错。假如不使用泛型,可以向容器中存入任意类型,容易出现类型转换异常。
- 不需要进行类型强制转换:使用泛型后容器可以记住存入容器中的对象的类型;
- 代码可读性提升:使用泛型后开发人员看一眼就知道容器中存放的是何种对象。
PS:本文参考了Java中泛型 类型擦除和关于List、List、List的区别,读者也可以点击查看原文的全部内容。
1. 泛型擦除
泛型擦除是指Java中的泛型只在编译期有效,在运行期间会被删除。也就是说所有泛型参数在编译后都会被清除掉。
public class Foo {
public void listMethod(List<String> stringList){
}
public void listMethod(List<Integer> intList) {
}
}
上面这段代码编译时会报方法重载错误,原因是上面两个方法的参数是泛型参数,在编译后会被泛型擦除,最后两个方法都会是 public void listMethod(List intList),所以会报重载错误的。
在编译器编译后,泛型的转换规则如下:
- List
、List 擦除后的类型为 List; - List
[]、List [] 擦除后的类型为 List[]; - List<? extends E>、List<? super E> 擦除后的类型为 List
; - List<T extends Serialzable & Cloneable> 擦除后类型为 List
。
有了上面的泛型擦除知识后,我们就可以理解下面的现象了:
- 泛型类的class对象相同
public static void main(String[] args) {
List<String> ls = new ArrayList<String>();
List<Integer> li = new ArrayList<Integer>();
System.out.println(ls.getClass() == li.getClass());
}
- 不能对泛型数组进行初始化
List<String>[] list = new List<String>[];
- instanceof 不允许存在泛型参数
List<String> list = new ArrayList<String>();
//在运行时list的泛型参数会被删除,所以判断不了类型
System.out.println(list instanceof List<String>)
2. 泛型的继承
泛型类的继承关系不是由泛型参数的继承关系来决定的。比如说Box类是一个泛型类,现在给这个类赋两个类型参数Integer和Number,虽然Integer是Number的子类,但是Box
如果想要两个泛型类之间具有继承关系,那么我们需要定义这个两个泛型类本身之间具有继承关系,而不是泛型参数之间具有泛型关系。JDK中有很多这种列子,我们可以拿List和ArrayList做一个列子。
public interface List<E> extends Collection<E> {
...
}
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
...
}
这时, 我们就可以说ArrayList
List<String> list = new ArrayList<String>();
但是需要注意的是,上面的赋值中泛型参数必须是一致的,下面的赋值形式编译期将会报编译错误。
List<Map> list = new ArrayList<HashMap>();
3. 泛型通配符使用
泛型统配符一般使用在泛型类的定义和泛型方法的定义。也就是说泛型通配符主要使用在类和方法的定义中。很少在具体的使用中用到(List<?>这种情况使用的很少),所以关于泛型统配符我们重点掌握使用通配符进行泛型的定义就好了。
//表示类型参数可以是任何类型
public class Apple<?>{}
//表示类型参数必须是A或者是A的子类
public class Apple<T extends A>{}
//表示类型参数必须是A或者是A的超类型
public class Apple<T supers A>{}
//定义泛型方法,表示参数必须是ApplicationContext的子类
public static <T extends ApplicationContext> List<T> getList(T type)
4. 泛型中KTVE的含义
如果点开JDK中一些泛型类的源码,我们会看到下面这些代码:
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、K和V都是什么意思呢?其实这些参数名称是可以任意指定,就想方法的参数名一样可以任意指定,但是我们通常会起一个有意义的名称,让别人一看就知道是什么意思。泛型参数也一样,E一般是指元素,用来集合类中。常见泛型参数名称有如下:
- E: Element (在集合中使用,因为集合中存放的是元素)
- T:Type(Java 类)
- K: Key(键)
- V: Value(值)
- N: Number(数值类型)
- ?: 表示不确定的java类型
- S、U、V:2nd、3rd、4th types
5. 泛型中Object的含义
6. List<Object>
和List的区别
List是原始类型,其引用变量可以接受任何对应List
List list1 = new ArrayList<Object>();
List list2 = new ArrayList<String>();
List