`
cantellow
  • 浏览: 842602 次
  • 性别: Icon_minigender_1
  • 来自: 草帽海贼团
社区版块
存档分类
最新评论

泛型のwhy&how

    博客分类:
  • Java
阅读更多

 

Why,Java为什么需要泛型?

如果我需要我的代码适用于多种类型,那么我可以使用object;如果我需要限定这个类型必须具有特定的约定,那么我可以定义接口或者抽象类。总之,我都可以解决,再加上Java的反射功能,没有泛型之前Collection库一样过得好好的。

 

那么java为什么还要加上泛型这样的语法糖呢?答案就是,在编译期间就检查类型转换是否正确,而不是拖延到运行时期。我想这也是Java泛型最吸引人的地方,它无法像C++模板那样强大,但也不是一无是处。

 

在泛型没有出现之前,容器里的实现都是通过超类object可以让任何对象都放进去,但是一旦放进去之后,特定类型就向上转型为object,从而丧失了类型信息。要想从里面得到特定的类型信息只有手动强制转换,但是这个转换在编译时期是无法预知的,运行时期完全有可能抛出ClassCastException异常。从软件工程的角度看,如果能在编译时期就检查类型转换十分正确,这是一种进步。

什么时候使用泛型?

如果你要编写可以应用于多种类型的代码(比如你重新定义一种数据结构,或者一种算法),你的头脑的第一反应是使用object,但是考虑前面所说的,你可以使用没有边界的泛型来代替。如果你的代码必须现定于使用某种接口或类的子类,那么可以考虑使用有边界的泛型来代替。

举例,java.lang.ThreadLocal的实现

ThreadLocal是用来为每一个线程提供一个副本,可以确保这个副本变量只在一个线程中被使用到。



 典型的用法是保持数据库连接connection的线程封闭性:

 

private static ThreadLocal<Connection> connectionHolder
    = new ThreadLocal<Connection>() {
        public Connection initialValue() {
            return DriverManager.getConnection(DB_URL);
        }
    };

public static Connection getConnection() {
    return connectionHolder.get();
}

ThreadLocal的实现也是采用了泛型,源代码中get方法返回的是T,基于擦除的认识,在运行时,这个T实际上看不到了,也不是真正的Connection,而是普通的object,你完全可以将上面的代码<Connection>忽略掉,把ThreadLocal源代码里的T看成object,那你可能要问,它的get方法为什么就知道返回的是Connection类型呢,原因是编译器编译后,在get方法自动加上了类型转换并添加了桥方法,能做到这一步的前提是在编译时期已经经过了类型检查,如果类型检查不通过就不会生成class文件,也就不会有后面这些步骤了。

How,擦除,一种折中方案

泛型其实算不上是一种语言特性,它只是java实现泛型技术的一种折中,至于为什么选择擦除,我猜可能是为了保持向后兼容,使得泛型代码和非泛型代码能够共存。为了保持向后兼容,泛型对JVM是不可见的,也就是JVM根本不需要做很多变化,因为既然你实现泛型的主要目标是提供编译时期的类型检查安全,那么很多工作都应该在编译时期做,实际上也是这样的,擦除也就是这么一个原理,在运行时期看不见任何泛型参数,JVM执行泛型代码就像执行以前的普通代码一样!(当然不做任何改变也是不可能的,可以参考http://icyfenix.iteye.com/blog/1021949)。

 

我觉得理解擦除原理最重要的是区分编译时期和运行时期:

擦除步骤

首先,编译器检查类型是否安全,就像下面的代码是通不过的:

 

List<String > list = new ArraryList<String>();
list.add(“cantellow”);
int integer = list.get(0);//compile error

 

然后,将参数化类型中的类型参数"擦除"erasure)掉,并且将类型变量用"上限(upper bound"取代,通常情况下这些上限是 Object。这里的类型变量是指实例域,本地方法域,方法参数以及方法返回值中用来标记类型信息的"变量",例如:实例域中的变量声明 A elem;,方法声明 Node (A elem){};,其中,A 用来标记 elem 的类型,它就是类型变量。

 

擦除规则:

如果泛型类型的类型变量没有限定(既是<T>) ,那么我们就用Object作为原始类型;

如果有限定(<T extends XClass>),我们就XClass作为原始类型;

如果有多个限定(<T extends XClass1&XClass2>),我们就用第一个边界的类型变量XClass1类作为原始类型; 

 

最后,添加类型转换并插入"桥方法"bridge method),以便覆盖(overridden)可以正常的工作。关于更详细的擦除步骤请参考:http://www.iteye.com/topic/549509

  • 大小: 30.9 KB
2
1
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics