本文源自参考《Think in Java》,多篇博文以及阅读源码的总结java
Java中的泛型每各人都在使用,可是它底层的实现方法是什么呢,为什么要这样实现,这样实现的优缺点有哪些,怎么解决泛型带来的问题。带着好奇,我查阅资料进行了初步的学习,在此与诸位探讨。编程
学过JAVA的人都知道泛型,明白大概怎么使用。在类上为:class 类名
{},在方法上为:public 安全void 方法名 (T x){}。泛型的实现使得类型变成了参数能够传入,使得类功能多样化。
具体可分为5种状况:编程语言
Class<T
>,List<T>
。<T extends Exception>
)JAVA的泛型是基于编译器实现的,使用了擦除的方法实现,这是由于java1.5以后才出现了泛型,为了保持向后兼容而作出的妥协。学习
所谓擦除就是JAVA文件在编译成字节码时类型参数会被擦除掉,单独记录在其余地方。而且用类型参数的父类代替原有的位置。
假设参数类型的占位符为T,擦除规则以下:code
<T>
擦除后变为Obecjt
<? extends A>
擦除后变为A
<? super A>
擦除后变为Object
这种规则叫作保留上界对象
编译器擦除类型参数后,经过JAVA的强制转换保证了类型参数在使用时的正确。如:在类型参数T中传入了类A,那么编译器会在全部类A将返回(抛出)类型参数T的代码处加上(A)进行强转.blog
举个栗子:get
ArrayList<String> list = new ArrayList<String>(); list.add("123"); String b = list.get(0);
在编译后会变成编译器
ArrayList list = new ArrayList();//没有参数即默认为Object list.add("123"); String b = (String) list.get(0);
而且会在带有类型参数类的子类中造成桥方法保证了多态性。
具体参考官方解释以下
- Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.
- Insert type casts if necessary to preserve type safety.
- Generate bridge methods to preserve polymorphism in extended generic types.
在带有类型参数的类内部,代码仍然按照参数类型擦除后的父类来处理。可是擦除存在一个问题,在这种机制下泛型是不变的,而没有逆变和协变。
协变和逆变网上有不少解释,显得模糊不清,我参考几个编程语言的官方解释后给出一个比较宽泛的定义。协变指可以使用比原始声明类型的派生程度更大(更具体的)的类型,逆变指可以使用比原始声明类型的派生程度更小(不太具体的)的类型。
如:
Object obj = new String("123");
这就是协变,将String这个更具体的(子类)类型赋给了本来较宽泛定义(父类)的类型Object。
JAVA不容许将父类赋给子类,天然Java不支持逆变。
网上不少博文说JAVA泛型也有逆变,我是不赞同的,那只是一种模拟的逆变,即有部分逆变的特性并且看起来像逆变,具体分析后文会给出
在JAVA中,
List<Integer> b = new ArrayList<Integer>() List<NumFber> a = b;
是没法经过编译器检查的。不容许这样作有一个很充分的理由:这样作将破坏要泛型的类型安全。若是可以将List<Integer>
赋给List<Number>
。那么下面的代码就容许将非Integer
的内容放入 List<Integer>
:
List<Integer> b = new ArrayList<Integer>(); List<Number> a = b; // illegal a.add(new Float(3.1415));
由于a
是List<Number>
,因此向其添加Float
彷佛是彻底可行的。可是若是a
实际是List<Integer>
,那么这就破坏了蕴含在b
中定义的类型声明 —— 它是一个整数列表,这就是泛型类型不能协变的缘由。但也所以使得泛型失去了多态的拓展性。
Java官方经过加入了通配符?
来解决泛型协变的问题。这样就能经过编译了:
List<Integer> b = new ArrayList<Integer>(); List<? extends Number> a = b;
能够解读为a
是一种带有Number
的List
集合类,在从a
中取出数据的时候统一当作Number
处理就好了。同时这也是符合里氏替换原则的
可是编译器会禁止你将将类Integer
放入a,即a.add(new Integer(1))//illegal
这也很合理,由于你声明的a
原本就没有限定a
包含的具体是哪一个Number
子类,所以不许任何变量的添加保证了泛型的安全性。
解决往a
添加对象的方法也很简单
List<Object> b = new ArrayList<Object>(); List<? super Number> a = b;
a
是某种Number
父类的List
集合类,将ArrayList<Object>
赋给a
也是合情合理的,Object
确实是Number
的父类。这也符合里氏替换原则的
(网上大部分博文说这就是逆变,可是仔细想一想逆变的官方定义,在JAVA中能够理解为:类T
是类S
的子类,而类A<T>
是类A<S>
的父类。仔细看看List<? super Number>
和List<Object>
的关系,在这里T
是Number
,而S
是Object
,可是List<? super Number>
从逻辑上来看真的是List<Object>
的子类吗,若是单纯从字面上来看List<? super Number>
是带有Number
父类的集合类,根据保留上界的擦除方法,应该擦除为List<Object>
,将一个List<Object>
赋给另外一个List<Object>
是不存在任何逆变的。我在疑惑之下去谷歌查阅了资料,也没有英文资料说明JAVA泛型里这属于逆变)