Java泛型(二)

1. Java的泛型通配符

  • 泛型通配符可以使用A-Z的任意一个字符,不影响泛型的效果,但会影响理解。

    class GenericClass2<A> implements GenericInterface<A>{
    
        @Override
        public A next() {
            return null;
        }
    }
    
  • 通常使用的通配符包括TKVE?,它们有一些约定俗成的含义:

    • ?表示不确定的 Java 类型。
    • T (type) 表示具体的一个Java类型
    • KV ,即key、value,表示Java键值中的健和值。
    • E (element) 代表

2. 无界通配符 ?

2.1 一个小需求

  • 老板说:list通过toString()得到的格式太丑了,我们自己定义一个通用的、打印list的工具方法吧

  • 小菜鸟的我,认为这很简单啊,噼噼啪啪敲出了如下代码

    public static void commonPrinter(List<Object> list) {
        for (Object obj : list) {
            System.out.print(obj + " ");
        }
        System.out.print("\n");
    }
    
  • 等到验证时,傻眼了,竟然不行 😂

    public static void main(String[] args) {
        List<Integer> intList = new ArrayList<>();
        intList.add(1);
        intList.add(2);
        commonPrinter(intList);  // 编译报错,泛型类型无法继承,即使Integer是Object的子类
    }
    
  • 报错信息如下:

    java: 不兼容的类型: java.util.List<java.lang.Integer>无法转换为java.util.List<java.lang.Object>

  • 奇了个怪了,Java中所有类都是Object的子类啊,我传入Integer类型的list,为啥提示类型不兼容??

  • 后来,仔细学习了下Java的泛型,知道了原因

    • commonPrinter中,已经为泛型类List传入了泛型类型Object
    • 调用工具方法时,传入List<Integer>,相当于下面的代码:
      List<Object> list = intList;
      
    • 泛型中,是不允许这样使用的:泛型擦除后,泛型类型是无法继承的,即使传入的泛型类型存在继承关系

2.2 如何正确实现上述需求?

  • 使用List<Object>无法接收各种类型的list,那是不是每个类我都需要单独为其创建一个工具方法?

  • 能不能有一种入参声明,使其能接收各种类型的list ?

  • 这时候,你需要使用无限定通配符?,将list定义为List<?> list,使其可以持有任意类型的list

  • 注意: 这里的任意类型是指List<Object>List<String>List<Integer>等,而非传入的泛型类型,但是泛型类型决定了list类型

    public static void main(String[] args) {
        List<Integer> intList = new ArrayList<>();
        intList.add(1);
        intList.add(2);
        // 传入List<Integer>类型的集合
        commonPrinter(intList);
        
        List<String> stringList = new ArrayList<>();
        stringList.add("hello");
        stringList.add("world");
        // 传入List<String>类型的集合
        commonPrinter(stringList);
    }
    
    public static void commonPrinter(List<?> list) {
        for (Object obj : list) {
            System.out.print(obj + " ");
        }
        System.out.print("\n");
    }
    

2.3 新的需求

  • 老板说:实现一个工具类,将list的最后一个元素复制后添加到list中

  • 这次我学聪明了:为了可以接收并处理各种类型的list,我使用无限定通配符?定义参数

    public static void addElement(List<?> list) {
        int size = list.size();
        if (list.size() > 0) {
            list.add(list.get(size - 1)); // 编译报错
        }
    }
    
  • 好奇怪啊,获取最后一个元素、再添加一次,有啥不对呢

  • 原因:

    • 传入的list不知道具体类型,也就是说传入的元素类型不确定
    • Java是不允许写入或读取不确定的类型
    • List<?>匹配到的元素类型是不确定的,往里面添加元素,很可能出现将String存入Integer集合中
    • 此时,写操作不被允许,除非添加null元素
    • 读操作是允许的,因为可以将所有的元素都视为Object类型

2.4 List和List<?>的区别

  • List<Object>表示只能接收泛型类型为Object的list对象,List<?>表示可以接收泛型类型为任意类型的list对象
  • List<Object>中,元素类型为Object是确定的,可以写入或读取Object类型的对象
  • List<?>中,元素类型是不确定的,写入操作不被允许(null除外),只允许读取操作(读取出来的一定是Object类型或其子类型,都可以使用Object接收)

3. 上界通配符<? extends T>

3.1 需求难度增大

  • 老板说:你这个工具类方法,可以接收任何类型的list,太宽泛了。我只需要你能接收Number类型或Number的子类型,帮我打印数字数组就可以了

  • 菜鸟挠挠头,难道我要对接收到的list,判断它里面的元素类型,是Number类型或Number的子类型才打印?

  • 仔细分析一下:老板是将泛型类型限定在了Number及其子类,是否有一种语法可以限定泛型类型为Number及其子类?

  • 这时候,可以使用上界通配符实现<? extends T>

    public static void commonPrinter(List<? extends Number> list) {
        for (Number number : list) {
            System.out.print(number + " ");
        }
        System.out.print("\n");
    }
    

3.2 上界通配符

  • <? extends T>表示传入泛型类型必须为T或T的子类、接口T或接口T的实现类

  • List<? extends Number>就限定了传入的list,及其泛型类型必须是Number或Number的子类。

  • 下面的代码,编译无法通过:java: 不兼容的类型: java.util.List<java.lang.String>无法转换为java.util.List<? extends java.lang.Number>

    List<String> stringList = new ArrayList<>();
    stringList.add("hello");
    commonPrinter(stringList);
    
  • 通过上界通配符,限定了泛型类型的上界,使得list存储的元素为Number或Number的子类

  • 上界通配符,适合频繁读取的场景

    • 读取时,元素都是Number或Number的子类型,可以统一向上转型为Number类型
    • 写入时,与?一样,由于类型不确定,可能会向List<Integer>中传入Double类型的对象,这是不允许的
    • 因此,上界通配符适合频繁读取的场景,不允许写入(null除外)

4. 下界通配符

4.1 自我挑战的一个需求

  • 既然可以通过上界通配符限定泛型类型为T或T的子类,是否可以限定泛型类型为T或T的基类呢?
  • 可以使用<? super T>,限定传入的泛型类型必需是类T或类T的基类。
  • 于是,兴致勃勃改写commonPrinter,限定其泛型类型为Number或Number的基类

4.2 下界通配符

  • 问题来了: 这时若读取list,元素类型是什么呢?

    • 传入的可能是Number,也可能是基类Object
    • 除非使用最终的基类Object进行接收(向上转型),不然无法获取list中的元素
    • 这样来看,下界通配符不适合读取的场景
      public static void commonPrinter(List<? super Number> list) {
          for (Object number : list) {
              System.out.print(number + " ");
          }
          System.out.print("\n");
      }
      
  • 另一个问题: 不能读,那能写吗?如果写也不能,没啥存在的价值。。。

    • 由于泛型类型是Number或Number的基类,向里面添加Number或Number的子类,都可以向上转型为Number或Number的基类
    • 这时,写入的类型是确定的,写入操作是允许
    • 这样来看,下界通配符适合频繁写入的场景
    public static void commonInsert(List<? super Number> list) {
        list.add(12);
    }
    

4.3 一些总结

  • 上界通配符,规定了泛型类型的上限:往里存入的元素类型不确定,读取出来的元素类型都可以向上转型为T
  • 下界通配符,规定类泛型类型的下限:写入的元素类型为T或T的子类,可以向上转型为T或T的基类;读取出来的元素类型不确定,除非使用Object进行接收
  • 因此,上界通配符适合读场景,下界通配符适合写场景

PECS(Producer Extends Consumer Super)原则

  • 将容器看做一个生产者,想从中读取元素,需要使用上界通配符“extends”实现
  • 将容器看做一个消费者,向往里写入元素,需要使用下界通配符“super”实现

参考文档


后续学习:

  • 泛型的常见面试问题
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值