• java 泛型


    今天来聊聊java中的泛型,泛型主要对类型进行限定。

    下面看一段代码

    代码

    /**
     * 图形
     */
    interface Shape {
        void draw();
    }
    
    /**
     * 圆形
     */
    class Circle implements Shape {
        @Override
        public void draw() {
        }
    }
    
    /**
     * 正方形
     */
    class Square implements Shape {
        @Override
        public void draw() {
    
        }
    }
    
    /**
     * 颜色
     */
    class Color{}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    没有指定泛型,默认list是可以添加任意Object类型。这就存在一定的风险,取数据的时候对类型进行转换需要避免类型不一致的问题。

    public class Test {
        public static void main(String[] args) {
            //没有指定泛型
            List list = new ArrayList<>();
            list.add(new Circle());
            list.add(new Square());
            //需要类型强制转换
            Shape shape = (Shape) list.get(0);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    泛型的用法是添加<>里边对类型进行限定,下边的例子ArrayList后边的<>无需添加类型,因为编译器会做类型推断。

    public class Test {
        public static void main(String[] args) {
            //指定泛型
            List<Shape> list = new ArrayList<>();
            list.add(new Circle());
            list.add(new Square());
            //编译失败
            list.add(new Color());
            //无需类型强制转换
            Shape shape =  list.get(0);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    类型擦除

    泛型里边有一个很重要的概念就是类型擦除,泛型的类型信息只存留在编译阶段,运行的时候是没有我们所设定的泛型信息的。

    使用 javap -c Test查看编译后的字节码信息

    示例1
    public class Test {
        public static void main(String[] args) {
            //没有指定泛型
            List list = new ArrayList<>();
            list.add(new Circle());
            list.add(new Square());
            //需要类型强制转换
            Shape shape = (Shape) list.get(0);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    上面代码编译完成后,查看字节码内容如下:

    Compiled from "Test.java"
    public class Test {
      public Test();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: new           #2                  // class java/util/ArrayList
           3: dup
           4: invokespecial #3                  // Method java/util/ArrayList."":()V
           7: astore_1
           8: aload_1
           9: new           #4                  // class Circle
          12: dup
          13: invokespecial #5                  // Method Circle."":()V
          16: invokeinterface #6,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
          21: pop
          22: aload_1
          23: new           #7                  // class Square
          26: dup
          27: invokespecial #8                  // Method Square."":()V
          30: invokeinterface #6,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
          35: pop
          36: aload_1
          37: iconst_0
          38: invokeinterface #9,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
          43: checkcast     #10                 // class Shape
          46: astore_2
          47: return
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    示例2
    public class Test {
        public static void main(String[] args) {
            //指定泛型
            List<Shape> list = new ArrayList<>();
            list.add(new Circle());
            list.add(new Square());
            //无需类型强制转换
            Shape shape =  list.get(0);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    上面代码编译完成后,查看字节码内容如下:

    Compiled from "Test.java"
    public class Test {
      public Test();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: new           #2                  // class java/util/ArrayList
           3: dup
           4: invokespecial #3                  // Method java/util/ArrayList."":()V
           7: astore_1
           8: aload_1
           9: new           #4                  // class Circle
          12: dup
          13: invokespecial #5                  // Method Circle."":()V
          16: invokeinterface #6,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
          21: pop
          22: aload_1
          23: new           #7                  // class Square
          26: dup
          27: invokespecial #8                  // Method Square."":()V
          30: invokeinterface #6,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
          35: pop
          36: aload_1
          37: iconst_0
          38: invokeinterface #9,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
          43: checkcast     #10                 // class Shape
          46: astore_2
          47: return
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    上面的两个示例,字节码内容完全一样。特别主要 38行,不管泛型指定什么类型,最终都会按照 Object 类型来处理。

    既然运行时,泛型所指定的类型信息已经被擦除,那运行时怎样实例信息呢?

    1.可以在构造函数传入Class信息

    2.利用反射

    //使用spring 提供的现成方法来获取第一个泛型值Class信息
    ResolvableType resolvableType = ResolvableType.forClass(this.getClass());
    Class<?> aClass =  resolvableType.getSuperType().getGenerics()[0].resolve();
    
    
    • 1
    • 2
    • 3
    • 4

    通配符

    表示无界通配符,可以支持所有类型,和原始类型有点相似。既然这样,那和不声明泛型又有什么区别呢?

    声明无界通配符,表示接收一个泛型类型。

    通配符?后只允许出现一个边界。

    通配符只允许出现在引用中(普通变量引用、形参),一般是用作或者。相对地,比如通配符不允许出现在泛型定义中(泛型类、泛型接口、泛型方法的< >里),class one {}这样是不允许的,类定义时继承泛型类时的< >里也不可以出现。在泛型类或泛型方法的{ }里还有泛型方法的形参上,配合占位符,甚至可以使用? extends T或者? super T这种形式来用作引用。

    new泛型类的时候也不可以使用通配符,比如new ArrayList()。泛型方法的显式类型说明也不可以使用通配符。

    通配符上界

    上界通配符声明方式,表示接收的类型只能是指定类型自身及子类。俗称协变,用在读数据场景。

    //声明接收类型只能是Shape 及其子类
    class Function<T extends Shape> {
    
    }
    public class Test {
        public static void main(String[] args) {
            Function function = new Function<Shape>();
            function = new Function<Circle>();
            //编译异常
            function = new Function<Color>();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    List<?> list = new ArrayList<>();
    //编译异常
    list.add(1);
    
    List<? extends Number> list2 = new ArrayList<>();
    //编译异常
    list2.add(1);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    上面这种写法编译错误,因为指定了通配符编译器并不知道确切类型是什么,所以传入任何类型都有问题。

    通配符下界

    下界通配符声明方式,表示接收的类型只能是指定类型自身及父类。俗称逆变,用在写数据场景。

    class Function {
        public void get(List<? super Circle> list){
            //添加元素类型是自身及其子类
            list.add(new Circle());
        }
    }
    public class Test {
        public static  void main(String[] args) {
            Function function = new Function();
            function.get(new ArrayList<Shape>());
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    参考:

    1.《java编程思想》
    
    2. 
    
    • 1
    • 2
    • 3
  • 相关阅读:
    为什么需要传递HINSTANCE给CreateWindow?
    CMU15445-project2-坑和收获总结
    ELK集群设置密码
    Ubuntu/Linux系统安装(非虚拟机)
    【网络安全】学过编程就是黑客?
    Rviz 使用Arbotix控制机器人运动
    【offer拿到手软系列】面试小贴士
    振弦采集模块数字接口
    C++(初阶四)类和对象
    LeetCode题目笔记——6228. 距离字典两次编辑以内的单词
  • 原文地址:https://blog.csdn.net/lp351539365/article/details/128086558