Java泛型的继承规则浅析

前提

由于在读源码以及自己实现一些通用的排序等时,总是会遇到<?>,当时查资料一直是一知半解。本次决定写出来相关的知识点。

内容
  1. 泛型的继承规则

    1. 首先,我们都清楚普通对象的向上转型,可以用父类来接受子类对象。

      Number a = 10;
      System.out.println(a.getClass().toString());
      a = 10.1;
      System.out.println(a.getClass().toString());
      
      // 结果
      /**
       * class java.lang.Integer
       * class java.lang.Double
       */ 
      
    2. 数组也可以。不过数组有特殊的保护机制。

      Number[] array = new Integer[]{1, 2, 3, 4};
      System.out.println(array.getClass().toString());
      array[0] = 1.0;
      System.out.println(array.getClass().toString());
      // 结果如下
      /**
       * class [Ljava.lang.Integer;
       * Exception in thread "main" java.lang.ArrayStoreException:
       * java.lang.Double
       * at Test2.main(Test2.java:14)
       */
      

      我们注意到,即使我们用父类来接受一个子类的数组对象,此时,其实数组的类型以及固定了,在示例中,其类型就是Integer[],所以我们向内部存放非Integer的数据时,就会出错,这是数组的保护机制。

    3. 重点来了,泛型

      先假设允许转换(下述代码编译不会通过)

      // 假设允许
      ArrayList<Integer> arrayList = new ArrayList<>();
      
      ArrayList<Number> array = arrayList;
      // 这句话就将成为可能
      array.add(10.5);
      

      这对于ArrayList<Integer>来说,存储10.5是不可能的。

      所以如果泛型允许向上转换,那就代表违反了泛型设计的本质,慢慢解决对象的向下转型的安全问题,以及使代码具有更好的可读性。

      所以,有人会说,那我就不用泛型接受了,去掉类型参数不就好了吗。

      ArrayList<Integer> arrayList = new ArrayList<>();
      arrayList.add(10);
      ArrayList numbers = arrayList;
      numbers.add(10.5);
      
      for (Object item: numbers) {
      	System.out.println((Integer) item);
      }
      

      这样至少编译可以通过吧!是的。但是这种情况,在list中就放入了混合的元素,你不能确定是哪个元素,所以在向下转型时会有安全问题。报错为Exception in thread "main" java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.Integer at Test2.main(Test2.java:24)

      这时候,你可能会疑惑,为什么integer数组在存放double时会出错,而ArrayList<Integer>在存放double时不会出错,只有在使用时会出错?后续我会分析ArrayList的源码,敬请关注。在这里我们只需要简单知道,ArrayList内部是使用Object[]来存放元素的,所以没问题。

      所以,无论ST有什么联系,其泛型类型例如List<S>List<T>往往没有什么关系。主要是因为这样可以提升安全性,避免一部分运行时的转换异常

  2. 那如果我两个子类都有一个共同的父类呢?比如我可以是BigCar,也可以是SmallCar,不让我使用Car来接收,使用List来接收的话,消除了类型,像上面一样,容易出错。

    所以,它为了防止你修改,使用List<? extends Car>来提供给你接收Car子类的功能,在这其中,你只能读取List<? extends Car>中的值,不能进行改变。

    看下面示例:

    public class Main {
        public static void main(String[] args) {
            List<Integer> list = new ArrayList<>();
            list.add(10);
            set(list);
            System.out.println(list);
        }
    
        public static void set(List<? extends Number> list) {
            // 这一行编译是不会通过的,不允许修改
            list.add(1);
            // 如果你非得告诉我,这么能改,
            // 其实你对比一下main的输出和这个内部值的输出就知道,并没有改。
            // 因为你是重新开辟了一块空间,在这个空间上修改罢了。
            list = new ArrayList<>();
            System.out.println("内部值" + list);
        }
    }
    

    那我有时候想改怎么办?我的set方法你规定不让用,所以就有了<? super BigCar>这样的超类型限定。

    public static void main(String[] args) {
        List<Number> list = new ArrayList<>();
        list.add(10);
        set(list);
        System.out.println(list);
        // 结果为[10, 10]
    }
    
    public static void set(List<? super Integer> list) {
    	list.add(10);
        // 啥也没有,不会打印
        System.out.println(list);	
    }
    

    所以,我们暂时可以简单理解为,<? extends xx>内只能获取值,不能写入,<? super xx>内只能向其中写入,不能获取值。

  3. 无限定通配符。

    有时我们能看到这个List<?>,貌似它和List差不多,但是。

    它的返回值是Object,在其内部也不能使用set方法。

    它对于某些简单的判定操作还是比较有用的。

后续,我会在博客更新ArrayList源码剖析,内部会涉及到这些东西。欢迎届时来与我交流。以上内容只是粗略记录了一下,收获还可以。如果有错误的地方,欢迎各位指正,谢谢。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值