🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
熟悉Java的同学应该会经常听到Java的一大特性,就是向后兼容。简单来说,就是老版本的Java文件编译后可以运行在新版本的JVM上。我们知道,Java一开始是没有泛型的,那么在Java 1.5之前,在程序中会出现大量的以下代码: ``` ArrayList list = new ArrayList(); //没有泛型 ``` 一般在没有泛型的语言上支持泛型,一般有两种方式,以集合为例: * 全新设计一个集合框架(全新实现现有的集合类或者创造新的集合类),不保证兼容老的代码,优点是不需要考虑兼容老的代码,写出更符合新标准的代码;缺点是需要适应新的语法,更严重的是可能无法改造老的业务代码。 * 在老的集合框架上改造,添加一些特性,兼容老代码的前提下,支持泛型。 很明显,Java选择了后种方式实现泛型,这也是有历史原因的,主要有以下两点原因: 1)在Java1.5之前已经有大量的非泛型代码存在了,若不兼容它们,则会让使用者抗拒升级,因为他要付出大量的时间去改造老代码; 2)Java曾经有过重新设计一个集合框架的教训,比如Java 1.1到Java1.2过程中的Vector到ArrayList, HashTable到HashMap,引起了大量使用者的不满。 所以,Java为了填补自己埋下的坑,只能用一种比较别扭的方式实现泛型,那便是类型擦除。 那么,为什么使用类型擦除实现泛型可以解决我们上面说的新老代码兼容的问题呢?我们先来看一下下面两行代码编译后的内容: ``` ArrayList list = new ArrayList(); //(1) ArrayList<String> stringList = new ArrayList<String>(); //(2) ``` 对应字节码: 0: new #2 // class java/util/ArrayList 3: dup 4: invokespecial #3 // Method java/util/ArrayList."<init>":()V 7: astore_1 8: new #2 // class java/util/ArrayList 11: dup 12: invokespecial #3 // Method java/util/ArrayList."<init>":()V 15: astore_2 我们发现**方式1和方式2声明的ArrayList再编译后的字节码是完全一样的**,这也说明了低版本编译的class文件在高版本的JVM上运行不会出现问题。既然泛型在编译后是会擦除泛型类型的,那么我们又为什么可以使用泛型的相关特性,比如类型检查、类型自动转换呢? **类型检查是编译器在编译前就会帮我们进行类型检查,所以类型擦除不会影响它**。那么类型自动转换又是怎么实现的呢?我们来看一个例子: ``` ArrayList<String> stringList = new ArrayList<String>(); String s = stringList.get(0); ``` 这段代码大家都应该很熟悉,get方法返回的值的类型就是List泛型参数的类型。来看一下ArrayList的get方法的源码: @SuppressWarnings("unchecked") E elementData(int index) { return (E) elementData[index]; //强制类型转换 } public E get(int index) { rangeCheck(index); return elementData(index); } 我们发现,背后也是通过强制类型转化来实现的。这点从编译后的字节码也可以得到验证: 0: new #2 // class java/util/ArrayList 3: dup 4: invokespecial #3 // Method java/util/ArrayList."<init>":()V 7: astore_1 8: aload_1 9: iconst_0 10: invokevirtual #4 // Method java/util/ArrayList.get:(I)Ljava/lang/Object; 获取的是Object 13: checkcast #5 // class java/lang/String强制类型转换 16: astore_2 17: return 所以可以得出结论,**虽然Java受限于向后兼容的困扰,使用了类型擦除来实现了泛型,但它还是通过其他方式来保证了泛型的相关特性**。