Loading

谈谈泛型和泛型擦除


所谓泛型,就是指在定义一个类、接口或者方法时可以指定类型参数。这个类型参数我们可以在使用类、接口或者方法时动态指定。

使用泛型可以给我们带来如下的好处

  • 编译时类型检查:当我们使用泛型时,加入向容器中存入非特定对象在编译阶段就会报错。假如不使用泛型,可以向容器中存入任意类型,容易出现类型转换异常。
  • 不需要进行类型强制转换:使用泛型后容器可以记住存入容器中的对象的类型;
  • 代码可读性提升:使用泛型后开发人员看一眼就知道容器中存放的是何种对象。

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

有了上面的泛型擦除知识后,我们就可以理解下面的现象了:

  1. 泛型类的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());  
}  
  1. 不能对泛型数组进行初始化
List<String>[] list = new List<String>[];  
  1. instanceof 不允许存在泛型参数
List<String> list = new ArrayList<String>();  
//在运行时list的泛型参数会被删除,所以判断不了类型
System.out.println(list instanceof List<String>)

2. 泛型的继承

泛型类的继承关系不是由泛型参数的继承关系来决定的。比如说Box类是一个泛型类,现在给这个类赋两个类型参数Integer和Number,虽然Integer是Number的子类,但是Box和Box没有任何关系,他们之间唯一的关系就是他们都是Object的子类.

如果想要两个泛型类之间具有继承关系,那么我们需要定义这个两个泛型类本身之间具有继承关系,而不是泛型参数之间具有泛型关系。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接口继承了Collection接口, 相信这样的例子我们已经见的不少了:

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<?>,并且可以添加任意类型的元素。但其缺点在于不安全性、不便利性、不表述性(不应该使用原生类型的原因)。

List list1 = new ArrayList<Object>();
List list2 = new ArrayList<String>();

List只能接收泛型参数是Object的List实现类:

List<Object> list1 = new ArrayList<Object>(); 
List<Object> list2 = new ArrayList<String>(); //编译报错

7. List<Object>List<?>的区别

List,即通配符类型,其引用变量,同样可以接受任何对应List的参数化类型,包括List。但是由于?表示List中的元素是未知的,所以不能向其中添加任何元素,从中取出的元素是Object类型,要通过手动转换才能得到原本的类型。List这种形式在定义泛型方法时比较常用:

//自定义泛型方法
public static void getData(List<?> data) 
//类型参数是Number或是其子类
public static void getUpperNumberData(List<? extends Number> data) 
public static void getUpperNumberData(List<? super Number> data)

List,即实际类型参数为Object的参数化类型,仅可以接受List和其本身类型。List已经明确确定List中的类型是Object或者其子类,从中取出元素时也不用强制转换成Object。

8. List<Object>List<T>之间的区别

List是Java中定义泛型方法的写法,比如:

public <T> List<T> getList(T type) {
    List<T> list = new ArrayList<T>();
    list.add(type);
    return list;
}

在代码运行之前不知道T是什么类型,只有在代码被运行之后才可以确定T的类型。

List是一种具体的用法,表示这个List中只能放Objec类或者是Object的子类。

9. 泛型的协变逆变

java中泛型是不变的,既不能协变,也不能逆变。但是可以使用通配符实现类似的效果。个人觉得太纠结这些概念问题没什么意思,这边就只记录下相关代码。

List<? extends Food> foodList = new ArrayList<>();
List<Apple> appleList = new ArrayList<>(); 
foodList = appleList; // ok 协变
List<? super Fruit> fruitList = new ArrayList<>();
List<Food> foodList = new ArrayList<>(); 
fruitList = foodList; // ok 逆变

公众号推荐

欢迎大家关注我的微信公众号「程序员自由之路」

posted @ 2020-03-12 17:37  程序员自由之路  阅读(7517)  评论(1编辑  收藏  举报