zoukankan      html  css  js  c++  java
  • Java 泛型和类型擦除

    一、概念

      在 Java 语言处于还没有出现泛型的版本时,只能通过 Object 是所有类型的父类和类型强制转换两个特点的配合来实现类型泛化。这样做有个缺点,就是只有程序员和运行期的虚拟机才知道这个 Object 到底是个什么类型的对象。在编译期,编译器无法检查这个 Object 的强制转换是否成功。因此,许多 ClassCastException 的风险就会转移到程序运行期之中。  

      泛型是 JDK 1.5 的一项新增特性,它的本质是参数化类型(Parametersized Type),就比如我们定义方法的时候,定义一个变量,称为形参,变量值根据传进去的实参的值不同而改变。而泛型的出现,就是为了解决类型也能根据传进去的类型改变的问题,也就是说所操作的数据类型被指定为一个参数。主要用在定义类、接口、方法的创建上,可以很好的减少代码的重复。

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

    tips:语法糖虽然不会提供实际性的功能改进,但是他们或能提高工作效率,或能提升语法的严谨性,或能减少编码出错的机会。常见的语法糖还有“内部类”、“自动装箱/拆箱”、“断言语句”、“枚举类”等等

    二、泛型使用

    1. 泛型中的标识符含义

     E - Element (在集合中使用,因为集合中存放的是元素)
     T - Type(Java 类)
     K - Key(键)
     V - Value(值)
     N - Number(数值类型)
    ? -  表示不确定的java类型
     S、U、V  - 2nd、3rd、4th types

    2、定义一个泛型方法

      首先,泛型的声明,必须在方法的修饰符(public, static, final, abstract 等)之后,返回值声明之前,可以声明多个泛型,用逗号隔开。

        public static <T1, T2> T1 print(List<T1> list, List<T2> list2) {
            return list.get(0);
        }

    3、定义一个泛型类

    @Data
    public class Box<T> {//这里可以定义多个泛型,用逗号分割
    
        private String name;
        private T t;
        
        /**
         * 泛型继承
         */
        public static class CircleBox extends Box<String> {
    
        }
    
        public static class SquareBox extends Box<Integer> {
            
        }
    }

    三、泛型擦除

      接下来我们来看一个泛型擦除的例子

        public static void main(String[] args) {
            HashMap<String, String> map = new HashMap<>();
            map.put("hello", "hello");
            map.put("world", "world");
            System.out.println(map.get("hello"));
            System.out.println(map.get("world"));
        }

      把这段 Java 代码编译成 Class 文件,然后再用字节码反编译工具进行反编译后,会发现所有泛型都不见了,泛型类型都变回了原生类型,只是在相应的地方做了类型强制转换。

       public static void main(String[] args) {
          HashMap map = new HashMap();
          map.put("hello", "hello");
          map.put("world", "world");
          System.out.println((String)map.get("hello"));
          System.out.println((String)map.get("world"));
       }

    四、泛型识别

      按照我们上面的说法,泛型在编译期间就被擦除了,那么在代码运行期各种场景(如反射等)下该如何识别参数化类型呢?总不能识别出来都是 Object 吧?这就要说到一个虚拟机属性 —— Signature,它的作用就是存储一个方法在字节码层面的特征签名,这个属性保存的参数类型并不是原生类型,而是包括了参数化类型的信息。

      因此,擦除法所谓的擦除,仅仅是对方法的 Code 属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们能通过反射等手段取得参数化类型的根本依据。

  • 相关阅读:
    〖Linux〗Kubuntu设置打开应用时就只在打开时的工作区显示
    〖Linux〗Kubuntu, the application 'Google Chrome' has requested to open the wallet 'kdewallet'解决方法
    unity, dll is not allowed to be included or could not be found
    android check box 自定义图片
    unity, ios skin crash
    unity, Collider2D.bounds的一个坑
    unity, ContentSizeFitter立即生效
    类里的通用成员函数应声明为static
    unity, Gizmos.DrawMesh一个坑
    直线切割凹多边形
  • 原文地址:https://www.cnblogs.com/jmcui/p/6942260.html
Copyright © 2011-2022 走看看