java 的泛型和类型擦除

一、什么是泛型

泛型 ,它只在程序源码中存在,在编译后的字节码文件中,就已经被替换为原来的原生类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此对于运行期的Java语言来说,ArrayList< int>与ArrayList< String>就是同一个类。所以说泛型技术实际上是Java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型被称为伪泛型。

二、什么是类型擦除

类型擦除: 1.将所有的泛型参数T用其边界(extends的)类型替换。(例如T extends Comparable) 2.移除所有的类型参数T。

三、泛型的声明

泛型声明的位置

  • 在类的声明上定义,例如RecyclerView 的 Adapter 类

    public abstract static class Adapter< VH extends ViewHolder> {

  • 在方法声明上定义,例如Collections 的 sort 方法

    public static <T extends Comparable<? super T>> void sort(List< T> list) {
    list.sort(null);
    }

  • 比较特殊的一点,由于擦除操作,泛型参数会被边界替换,所以不支持声明的super操作

    这样声明是合法的: class A< T extends Number>
    这样声明是非法的:class A< T super Integer>

四、java 编译器处理泛型的过程

  • JDK1.5版本之后class文件的属性表中添加了Signature和LocalVariableTypeTable等属性来支持泛型识别的问题
  • 所以各种ide 查看 class 文件时,是进行了类型复原的,所以你会疑问(没有进行类型擦除啊!!!)
  • 推荐使用jad 进行反编译 下载地址 https://varaneckas.com/jad/
    public static  void main(String args[]){
         String s = max(" nihao");
         List<String>  list = new ArrayList<>();
         maxTest4(list);
    }

    /***
     * 方法定义了 泛型 参数
     * @param a
     * @param <T>
     */
    public  static <T> T max( T a ){
        return a;
    }
    
    /***
     * 方法定义了 泛型   ,并且 添加 了 上界
     * @param a
     * @param <T>
     */
    public  static <T extends Integer> T maxOne( T a){
        return a;
    }

    /***
     *  方法定义了泛型, 并且泛型的上界是泛型类 。此是 泛型类 的 泛型 可以设置为以下 三种情况
     *  一 、 确切类型
     * @param a
     * @param <T>
     * @return
     */
    public  static <T extends Comparable<Integer>> T maxTwo( T a){
        return a;
    }
    
    /***
     *  方法定义了泛型, 并且泛型的上界是泛型类 。此是 泛型类 的 泛型 可以设置为以下 三种情况
     *  二、  方法定义的 泛型 T
     * @param a
     * @param <T>
     * @return
     */
    public  static <T extends Comparable< T >> T maxTest( T a){
        return a;
    }
    
    /***
     *  方法定义了泛型, 并且泛型的上界是泛型类 。此是 泛型类 的 泛型 可以设置为以下 三种情况
     *  三、  带通配符的泛型
     * @param a
     * @param <T>
     * @return
     */
    public  static <T extends Comparable< ? super Integer >> T maxTest2( T a){
        return a;
    }
    
    /***
     * 函数没有定义泛形,函数形参的类型 是 泛型类
     * @param comparable
     * @return
     */
    public static int maxTest3(Comparable<? super Integer> comparable){
        return comparable.compareTo(20);
    }
    
    public static void maxTest4(List<?> list) {

    }

以下是反编译 class 文件后的代码。 验证了上文所说的类型擦除: 1.将所有的泛型参数用其最左边界(最顶级的父类型)类型替换


    public static void main(String args[])
    {
        String s = (String)max(" nihao");
        List list = new ArrayList();
        maxTest4(list);
    }

    public static Object max(Object a)
    {
        return a;
    }

    public static Integer maxOne(Integer a)
    {
        return a;
    }

    public static Comparable maxTwo(Comparable a)
    {
        return a;
    }

    public static Comparable maxTest(Comparable a)
    {
        return a;
    }

    public static Comparable maxTest2(Comparable a)
    {
        return a;
    }

    public static int maxTest3(Comparable comparable)
    {
        return comparable.compareTo(Integer.valueOf(20));
    }

    public static void maxTest4(List list1)
    {
    }

五、泛型与原始类型

无限制通配符 Set< ?> 与原始类型 Set 之间有什么区别? 问号真的给你放任何东西吗? 这不是要点,但通
配符类型是安全的,原始类型不是。 你可以将任何元素放入具有原始类型的集合中,轻易破坏集合的类型不变性。

你不能把任何元素(除 null 之外)放入一个 Collection< ?> 中。 而且无法预测你会get 到怎么的对象。

使用 完全通配符

以Collections 的源码为例 :强转为原生类型

    public static void swap(List<?> list, int i, int j) {
        // instead of using a raw type here, it's possible to capture
        // the wildcard but it will require a call to a supplementary
        // private method
        final List l = list;
        l.set(i, l.set(j, l.get(i)));
    }

或者 使用无泛型的Iterator

    public static void reverse(List<?> list) {
        int size = list.size();
        if (size < REVERSE_THRESHOLD || list instanceof RandomAccess) {
            for (int i=0, mid=size>>1, j=size-1; i<mid; i++, j--)
                swap(list, i, j);
        } else {
            // instead of using a raw type here, it's possible to capture
            // the wildcard but it will require a call to a supplementary
            // private method
            ListIterator fwd = list.listIterator();
            ListIterator rev = list.listIterator(size);
            for (int i=0, mid=list.size()>>1; i<mid; i++) {
                Object tmp = fwd.next();
                fwd.set(rev.previous());
                rev.set(tmp);
            }
        }
    }

六、限定通配符

限定通配符为 extends 和 super

  • java 的编译器 不会认为 List< Integer> 是 List< Number> 的子类型
        List<Number> list1 ;
        List<Integer> list2 = new ArrayList<>();
        list1 = list2;    // 这行代码会编译报错
        List<Number> list0  =null;
        List<Integer> list1  =null;
        List<? extends Number> list2 = null ;
        list2 = list1;
        list2 = list0;

使用 限制通配符 extends 可以解决这种问题 ,并且保证了类型安全。
也就是编译器 认为 List< Number> 是 List< ? extends Number> 的子类型
List< Integer> 是 List<? extends Number> 的子类型

  • super 关键字
        List<? super Integer> super1 = null;
        List<Integer> super2 = null;
        List<Number> super3 = null;
        super1 = super2;
        super1 = super3;

也就是编译器 认为 List< Integer> 是 List< ? super Integer> 的子类型
List< Number> 是 List<? super Integer> 的子类型
以 mySort 方法为例。

    // 自己定义的 sort 方法 ,只做编译测试
    public static <T extends Comparable< T>> void mySort(List<T> list) {
        return;
    }
    public static class Food implements Comparable<Food>{
        public int price;
        public Food(int price) {
            this.price = price;
        }

        @Override
        public int compareTo(@NonNull Food o) {
            return price - o.price;
        }
    }

    public static class Milk extends Food{

        public Milk(int price) {
            super(price);
        }
    }

<T extends Comparable< T>> 表示:
类型 T 必须实现 Comparable 接口,并且这个接口的类型是 T。T 的实例之间才能相互比较大小。
由于是Milk 从父类那里继承实现的是Comparable< Food> ,以下代码编译会报错

        List<Milk> milkList = new ArrayList<>();
        mySort(milkList);    // 编译报错
        错误: 无法将类 GenericTest中的方法 mySort应用到给定类型;
需要: List<T>
找到: List<Milk>
原因: 推断类型不符合上限
推断: Milk
上限: Comparable<Milk>
其中, T是类型变量:
T扩展已在方法 <T>mySort(List<T>)中声明的Comparable<T>

将上面的函数改为 T extends Comparable< ? super T >

    public static <T extends Comparable< ? super T>> void mySort(List<T> list) {
        return;
    }

执行

        List<Milk> milkList = new ArrayList<>();
        mySort(milkList);    // 编译通过

编译器推断类型T 是 Milk 。 Milk 实现了 Comparable< Food> 接口
编译器 认为 Comparable< Food> 是 Comparable< ? super Milk> 的子类。
编译通过。

参考资料 Java的类型擦除

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值