Java 泛型系列三:泛型中的继承规则与通配符

589 阅读2分钟

Java 泛型系列三:泛型中的继承规则与通配符

本文概述:

  • 本文为 Java 泛型系列第三篇文章,文章重点:泛型对继承关系的破坏、 泛型通配符extends 使用中编译器对get/set 的限定、泛型通配符super 下界反转问题(本文精华之所在);

泛型类型的继承规则

  • 环境准备:FuClass、ZiClass、Pair;

    • FuClass.java

       package generic;
       ​
       public class FuClass {
       }
      
    • ZiClass.java

       package generic;
       ​
       public class ZiClass extends FuClass {
       }
      
  • Pair<父类> ma = Pair<子类> 报错,两者之间没有继承关系

    • Pair.java

      image-20220811224357527

  • 泛型类可以继承或扩展自其它泛型类,比如说List 与 ArrayList

    image-20220811224916333

  • 泛型类指定类型会破坏原有的继承关系

    • A 继承 B,但Pair< A > 与 Pair 不具有继承关系

    image-20220811225355608

泛型:通配符类型 extends

  • 安全访问数据

  • 细节:

    • 限定了实参的上界,可以自己留着,也可以向下传,但是不能向上传
    • 通配符是用在方法上,不能用在类上
  • 编译器对get/set 的限定

    • 总结:限定上界用于安全访问数据

      • extends 限定实参类型,用作读取数据;
      • 一般不用于set 数据,
      • 因为编译器只知道上界是什么(可以用上界去接收),但是不知道?具体代表什么,所以不允许set 数据
    • 环境准备:FuClass.java、ZiClass.java

      • FuClass.java

         package generic;
         ​
         public class FuClass {
         }
        
      • ZiClass.java

         package generic;
         ​
         public class ZiClass extends FuClass {
         ​
         }
         ​
        
    • 关键代码:

      image-20220811233448711

    • 完整测试代码:

       package generic;
       ​
       public class Pair<T> {
       ​
           private T data;
       ​
           public T getData() {
               return data;
           }
       ​
           public void setData(T data) {
               this.data = data;
           }
       ​
           public static void main(String[] args) {
       ​
               Pair<? extends FuClass> ma = new Pair<>();
       ​
               //测试get 方法
               FuClass fuClass = ma.getData();
               ma.setData("name");//报错
           }
       }
      

泛型:通配符 super

  • 安全插入数据

  • super 使用过程:简单使用

    • Pair.java 可看做是一容器
     package generic;
     ​
     public class Pair<T> {
     ​
         private T data;
     ​
         public T getData() {
             return data;
         }
     ​
         public void setData(T data) {
             this.data = data;
         }
     ​
         public static void test(Pair<? super ZiClass> p){
             System.out.println(p.toString());
         }
     ​
         public static void main(String[] args) {
             //泛型类在使用时,指定其泛型类型
             Pair<ZiClass> ziClassPair = new Pair<>();
             Pair<FuClass> fuClassPair = new Pair<>();
             
             test(ziClassPair);
             test(fuClassPair);
         }
     }
    
  • super 的难点问题:下界反转问题

    • super 在泛型中指定了实参的下界,比如说,某一方法参数类型为 容器类<? super A>,那么调用这个方法时:详见上文代码

      1. 实例化泛型类具体种类
      2. 只能传入类A 以及类A 的父类
    • 但是,如果在使用时没有指定容器的具体泛型时,则会发生下界反转问题

      • 现象:

        • 只能传入类A 或者类A 的子类、孙类实例;
    • super 对于 get 的兼容性

      • 只能确定从容器中拿到的一定是Object 的子类
       package generic;
       ​
       public class Pair<T> {
       ​
           private T data;
       ​
           public T getData() {
               return data;
           }
       ​
           public void setData(T data) {
               this.data = data;
           }
       ​
           public static void test(Pair<? super ZiClass> p){
               System.out.println(p.toString());
           }
       ​
           public static void main(String[] args) {
       ​
               Pair<? super ZiClass> pair = new Pair<>();
               //测试get 方法
               Object o = pair.getData();
               //以下代码全数报错
               FuClass f = pair.getData();
               ZiClass z = pair.getData();
               SunClass s = pair.getData();
       }
      
    • super 对 set 的兼容性

      • 虽然说,编译器不知道到底会插入什么类型,只是知道插入的一定是ZiClass 本身或者ZiClass 的父类,但是不确定到底是什么,所以编译器不允许插入;但是,ZiClass 本身或者其子类一定可以安全地插入,因为可以安全转型,这就是多态的一大应用(容器里面装的是父类引用,那么,我们就将子类对象传给它!!!)
      • 关键代码:

      image-20220812000333395