聊聊Java的泛型及实现
作者:suemi94 发布时间:[ 2017/3/20 14:51:16 ] 推荐标签:测试开发技术 Java
并且,还有一点也许会有疑问,子类中的方法 Object getValue()和Date getValue()是同 时存在的,可是如果是常规的两个方法,他们的方法签名是一样的,也是说虚拟机根本不能分别这两个方法。如果是我们自己编写Java代码,这样的代码是无法通过编译器的检查的,但是虚拟机却是允许这样做的,因为虚拟机通过参数类型和返回类型来确定一个方法,所以编译器为了实现泛型的多态允许自己做这个看起来“不合法”的事情,然后交给虚拟器去区别。
我们再看一个经常出现的例子。
class A {
Object get(){
return new Object();
}
}
class B extends A {
@Override
Integer get() {
return new Integer(1);
}
}
public static void main(String[] args){
A a = new B();
B b = (B) a;
A c = new A();
a.get();
b.get();
c.get();
}
反编译之后的结果
17: invokespecial #5 // Method com/suemi/network/test/A."<init>":()V
20: astore_3
21: aload_1
22: invokevirtual #6 // Method com/suemi/network/test/A.get:()Ljava/lang/Object;
25: pop
26: aload_2
27: invokevirtual #7 // Method com/suemi/network/test/B.get:()Ljava/lang/Integer;
30: pop
31: aload_3
32: invokevirtual #6 // Method com/suemi/network/test/A.get:()Ljava/lang/Object;
实际上当我们使用父类引用调用子类的get时,先调用的是JVM生成的那个覆盖方法,在桥接方法再调用自己写的方法实现。
泛型参数的继承关系
在Java中,大家比较熟悉的是通过继承机制而产生的类型体系结构。比如String继承自Object。根据Liskov替换原则,子类是可以替换父类的。当需要Object类的引用的时候,如果传入一个String对象是没有任何问题的。但是反过来的话,即用父类的引用替换子类引用的时候,需要进行强制类型转换。编译器并不能保证运行时刻这种转换一定是合法的。这种自动的子类替换父类的类型转换机制,对于数组也是适用的。 String[]可以替换Object[]。但是泛型的引入,对于这个类型系统产生了一定的影响。正如前面提到的List<String>是不能替换掉List<Object>的。
引入泛型之后的类型系统增加了两个维度:一个是类型参数自身的继承体系结构,另外一个是泛型类或接口自身的继承体系结构。第一个指的是对于 List<String>和List<Object>这样的情况,类型参数String是继承自Object的。而第二种指的是 List接口继承自Collection接口。对于这个类型系统,有如下的一些规则:
相同类型参数的泛型类的关系取决于泛型类自身的继承体系结构。即List<String>可以赋给Collection<String> 类型的引用,List<String>可以替换Collection<String>。这种情况也适用于带有上下界的类型声明。 当泛型类的类型声明中使用了通配符的时候, 这种替换的判断可以在两个维度上分别展开。如对Collection<? extends Number>来说,用来替换他的引用可以在Collection这个维度上展开,即List<? extends Number>和Set<? extends Number>等;也可以在Number这个层次上展开,即Collection<Double>和 Collection<Integer>等。如此循环下去,ArrayList<Long>和 HashSet<Double>等也都可以替换Collection<? extends Number>。
如果泛型类中包含多个类型参数,则对于每个类型参数分别应用上面的规则。理解了上面的规则之后,可以很容易的修正实例分析中给出的代码了。只需要把List<Object>改成List<?>即可。List<String>可以替换List<?>的子类型,因此传递参数时不会发生错误。
个人认为这里对上面这种情形使用子类型这种说法来形容这种关系是不当的,因为List<String>等本质上来说不能算作类型,只是对List类型加上了编译器检查约束,也不存在子类型这种说法。只能用是否在赋值时能够进行类型转换来说明。
泛型使用中的注意点
运行时型别查询
// 错误,为类型擦除之后,ArrayList<String>只剩下原始类型,泛型信息String不存在了,无法进行判断
if( arrayList instanceof ArrayList<String>)
if( arrayList instanceof ArrayList<?>) // 正确
异常中使用泛型的问题
不能抛出也不能捕获泛型类的对象。事实上,泛型类扩展Throwable都不合法。为什么不能扩展Throwable,因为异常都是在运行时捕获和抛出的,而在编译的时候,泛型信息全都会被擦除掉。类型信息被擦除后,那么多个使用不同泛型参数地方的catch都变为原始类型Object,那么也是说,多个地方的catch变的一模一样,这自然不被允许。
不能再catch子句中使用泛型变量。
public static <T extends Throwable> void doWork(Class<T> t){
try{
...
}catch(T e){ //编译错误 T->Throwable,下面的永远不会被捕获,所以不被允许
...
}catch(IndexOutOfBounds e){
}
}
不允许创建泛型类数组
Pair<String,Integer>[] table = new Pair<String,Integer>[10];// 编译错误
Pair[] table = new Pair[10];// 无编译错误
由于数组必须携带自己元素的类型信息,在类型擦除之后,Pair<String,Integer>数组变成了Pair<Object,Object>数组,数组只能携带它的元素是Pair这样的信息,但是并不能携带其泛型参数类型的信息,所以也无法保证table[i]赋值的类型安全。编译器只能禁用这种操作。
泛型类中的静态方法和静态变量
泛型类中的静态方法和静态变量不可以使用泛型类所声明的泛型类型参数。
public class Test2<T> {
public static T one; //编译错误
public static T show(T one){ //编译错误
return null;
}
}
因为泛型类中的泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用。对象都没有创建,如何确定这个泛型参数是何种类型,所以当然是错误的。
类型擦除后的冲突
class Pair<T> {
public boolean equals(T value) {
return null;
}
}
方法重定义了,同时存在两个equals(Object o)。
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系SPASVO小编(021-61079698-8054),我们将立即处理,马上删除。
相关推荐
Java性能测试有哪些不为众人所知的原则?Java设计模式??装饰者模式谈谈Java中遍历Map的几种方法Java Web入门必知你需要理解的Java反射机制知识总结编写更好的Java单元测试的7个技巧编程常用的几种时间戳转换(java .net 数据库)适合Java开发者学习的Python入门教程Java webdriver如何获取浏览器新窗口中的元素?Java重写与重载(区别与用途)Java变量的分类与初始化JavaScript有这几种测试分类Java有哪四个核心技术?给 Java开发者的10个大数据工具和框架Java中几个常用设计模式汇总java生态圈常用技术框架、开源中间件,系统架构及经典案例等
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11热门文章
常见的移动App Bug??崩溃的测试用例设计如何用Jmeter做压力测试QC使用说明APP压力测试入门教程移动app测试中的主要问题jenkins+testng+ant+webdriver持续集成测试使用JMeter进行HTTP负载测试Selenium 2.0 WebDriver 使用指南