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
class Box<T> {//这里可以定义多个泛型,用逗号分割

    private String name;
     T t;
    
    /**
     * 泛型继承
     */
    static class CircleBox extends Box<String> {

    }

    class SquareBox extends Box<Integer> {
        
    }
}

三、泛型擦除

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

    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 文件,然后再用字节码反编译工具进行反编译后,会发现所有泛型都不见了,泛型类型都变回了原生类型,只是在相应的地方做了类型强制转换。

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

四、泛型识别

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

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

 

相关文章

摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠...
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠...
今天犯了个错:“接口变动,伤筋动骨,除非你确定只有你一个...
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:...
本文目录 线程与多线程 线程的运行与创建 线程的状态 1 线程...