聊聊Java的泛型及实现

发表于:2017-3-20 11:05

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:suemi94    来源:51Testing软件测试网采编

分享:
  并且,还有一点也许会有疑问,子类中的方法 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)。
22/2<12
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号