Java 泛型

来源:互联网 发布:网络流行语盘点 编辑:程序博客网 时间:2024/06/11 22:29

泛型是JDK1.5的新的特性,它主要解决安全问题,主要好处就是让编译器保留参数的类型信息,执行类型检查,执行类型转换(casting)操作,编译器保证了这些类型转换(casting)的绝对无误。看下面的例子:

       /** ***** 不使用泛型类型 ****** */ 
        List list1 =  new ArrayList(); 
        list1.add(ks);                                   // 编译器不检查值 
        String str1 = (String)list1.get(0);  // 需手动强制转换,如转换类型与原数据类型不一致将抛出ClassCastException异常 
         
         /** ***** 使用泛型类型 ****** */ 
        List<String> list2 =  new ArrayList<String>(); 
        list2.add("value");                  // [类型安全的写入数据] 编译器检查该值,该值必须是String类型才能通过编译 
        String str2 = list2.get(0);  // [类型安全的读取数据] 不需要手动转换 

 Java 中的泛型只存在于编译期,在将 Java 源文件编译完成 Java 字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除(type erasure)。

        List<String>    list1 =  new ArrayList<String>(); 
        List<Integer> list2 =  new ArrayList<Integer>(); 
         
        System.out.println(list1.getClass() == list2.getClass());  //  输出结果: true 
        System.out.println(list1.getClass().getName());  //  输出结果: java.util.ArrayList 
        System.out.println(list2.getClass().getName());  //  输出结果: java.util.ArrayList 

限定通配符的上界:

        ArrayList<?  extends Number> collection =  null
         
        collection =  new ArrayList<Number>(); 
        collection =  new ArrayList<Short>(); 
        collection =  new ArrayList<Integer>(); 
        collection =  new ArrayList<Long>(); 
        collection =  new ArrayList<Float>(); 
        collection =  new ArrayList<Double>(); 

 ? extends XX,XX 类是用来限定通配符的上界,XX 类是能匹配的最顶层的类,它只能匹配 XX 类以及 XX 类的子类。在以上代码中,Number 类的实现类有:
AtomicInteger、AtomicLong、 BigDecimal、 BigInteger、 Byte、 Double、 Float、 Integer、 Long、 Short ,因此以上代码均无错误。

限定通配符的下界:

        ArrayList<?  super Integer> collection =  null
         
        collection =  new ArrayList<Object>(); 
        collection =  new ArrayList<Number>(); 
        collection =  new ArrayList<Integer>(); 
        
 ? super XX,XX 类是用来限定通配符的下界,XX 类是能匹配的最底层的类,它只能匹配 XX 类以及 XX 类的超类,在以上代码中,Integer 类的超类有:
Number、Object,因此以上代码均能通过编译无误。

通过反射获得泛型的实际类型参数:

    这个就有点难度了,上面已经说到,泛型的类型参数会在编译完成以后被擦除,那在运行期间还怎么来获得泛型的实际类型参数呢?这个是有点难度了吧?似乎不可能实现的样子,

    其实不然,java.lang.Class 类从 Java 1.5 起(如果没记错的话),提供了一个 getGenericSuperclass() 方法来获取直接超类的泛型类型,这就使得获取泛型的实际类型参数成为

    了可能,下面来看一段代码,这段代码很精辟,很有用,大家一定要学到手哈:


package test; 

import java.lang.reflect.ParameterizedType; 
/** 
 * -----------------------------------------
 * @描述  泛型的实际类型参数
 * @作者  fancy
 * @邮箱  fancydeepin@yeah.net
 * @日期  2012-8-25 <p>
 * -----------------------------------------
 
 */ 
public  class Base<T> { 

     private Class<T> entityClass; 
     
     // 代码块,也可将其放置到构造子中 
    { 
        entityClass =(Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0]; 
             
    } 
     
     // 泛型的实际类型参数的类全名 
     public String getEntityName(){ 
         
         return entityClass.getName(); 
    } 
     
     // 泛型的实际类型参数的类名 
     public String getEntitySimpleName(){ 
         
         return entityClass.getSimpleName(); 
    } 

     // 泛型的实际类型参数的Class 
     public Class<T> getEntityClass() { 
         return entityClass; 
    } 
     


以上代码的精华全在这句:(Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];

实际上,这句话咋看起来很难看的明白,理解起来就更加的吃力了,下面容我来将这句复杂的代码拆分开来,理解起来可能会好些:


     // 代码块,也可将其放置到构造子中 
    { 
         // entityClass =(Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0]; 
         try { 
            Class<?> clazz = getClass();  // 获取实际运行的类的 Class 
            Type type = clazz.getGenericSuperclass();  // 获取实际运行的类的直接超类的泛型类型 
             if(type  instanceof ParameterizedType){  // 如果该泛型类型是参数化类型 
                Type[] parameterizedType = ((ParameterizedType)type).getActualTypeArguments(); // 获取泛型类型的实际类型参数集 
                entityClass = (Class<T>) parameterizedType[0];  // 取出第一个(下标为0)参数的值 
            } 
        }  catch (Exception e) { 
            e.printStackTrace(); 
        } 
             
    } 
    

注意,获取 Class 实例的时候是用 getClass(),而不是用 Base.class,获取 Class 的方式有三种,这是其中的两种,还有一种是 Class.forName("类全名"),如需了解反射的基础知识

请前往上一篇随笔 java 反射基础 

那么,Base.class 与 getClass(),这两个方法来获取类的字节码的时候,有什么不一样的地方呢?当然有不一样的地方了,Base.class 是写死了的,它得到的永远是 Base 类的字节码,

而 getClass() 方法则不同,在上面代码注释中的第一、二行注释我用了“实际运行的类”6个字,这几个字很重要,一定要理解,如果无法理解,下面的你可能就看不懂了。

为了方便大家的理解,下面插加一个小例子来加以说明 类.class 与 getClass() 两种方法来获取类的字节码有什么区别:


package test; 
/** 
 * -----------------------------------------
 * @描述  超类
 * @作者  fancy
 * @邮箱  fancydeepin@yeah.net
 * @日期  2012-8-25 <p>
 * -----------------------------------------
 
 */ 
public  class Father { 

     public Father (){ 
         
        System.out.println("Father 类的构造子:"); 
        System.out.println("Father.class :" + Father. class); 
        System.out.println("getClass()      :" + getClass()); 
    } 
     



package test; 

/** 
 * -----------------------------------------
 * @描述  超类的子类(超类的实现类)
 * @作者  fancy
 * @邮箱  fancydeepin@yeah.net
 * @日期  2012-8-25 <p>
 * -----------------------------------------
 
 */ 
public  class Children  extends Father{ 

     



package test; 
/** 
 * -----------------------------------------
 * @描述  测试类
 * @作者  fancy
 * @邮箱  fancydeepin@yeah.net
 * @日期  2012-8-25 <p>
 * -----------------------------------------
 
 */ 
public  class Test { 

     public  static  void main(String[] args){ 
         
         new Children();  // 实际运行的类是Children(Father类的子类或者说是实现类) 
    } 
     


后台打印输出的结果:

Father 类的构造子: 
Father. class : class test.Father 
getClass()      : class test.Children 

 从打印出的结果看来,类.class 与 getClass() 的区别很明了了,getClass() 获取的是实际运行的类的字节码,它不一定是当前类的 Class,有可能是当前类的子类的 Class,具体是哪

个类的 Class,需要根据实际运行的类来确定,new 哪个类,getClass() 获取的就是哪个类的 Class,而 类.class 获取得到的 Class 永远只能是该类的 Class,这点是有很大的区别的。

这下“实际运行的类”能理解了吧,那么上面的那段被拆分的代码也就不难理解了,getClass() 理解了那 clazz.getGenericSuperclass() 也就没什么问题了吧,千万不要以为 

clazz.getGenericSuperclass() 获取得到的是 Object 类那就成了,实际上假如当前运行的类是 Base 类的子类,那么 clazz.getGenericSuperclass() 获取得到的就是 Base 类。

再者就是最后一句,(Class<T>) parameterizedType[0],怎么就知道第一个参数(parameterizedType[0])就是该泛型的实际类型呢?很简单,因为 Base<T> 的泛型的类型

参数列表中只有一个参数,所以,第一个元素就是泛型 T 的实际参数类型。

其余的已经加了注释,看一下就明白了,这里不多解释,下面 Base 这个类是不是就直接能使用了呢?来看一下就知道了:


package test; 
/** 
 * -----------------------------------------
 * @描述  测试类
 * @作者  fancy
 * @邮箱  fancydeepin@yeah.net
 * @日期  2012-8-25 <p>
 * -----------------------------------------
 
 */ 
public  class Test { 

     public  static  void main(String[] args){ 
         
        Base<String> base =  new Base<String>(); 
        System.out.println(base.getEntityClass());                         // 打印输出 null
    
 //     System.out.println(base.getEntityName());                 // 抛出 NullPointerException 异常
    
 //     System.out.println(base.getEntitySimpleName());  // 抛出 NullPointerException 异常 
    } 
     


从打印的结果来看,Base 类并不能直接来使用,为什么会这样?原因很简单,由于 Base 类中的 clazz.getGenericSuperclass() 方法,如果随随便便的就确定 Base 类的泛型的类型

参数,则很可能无法通过 Base 类中的 if 判断,导致 entityClass 的值为 null,像这里的 Base<String>,String 的 超类是 Object,而 Object 并不能通过 if 的判断语句。

Base 类不能够直接来使用,而是应该通过其子类来使用,Base 应该用来作为一个基类,我们要用的是它的具体的子类,下面来看下代码,它的子类也不是随便写的:


package test; 
/** 
 * -----------------------------------------
 * @描述  Base类的实现类
 * @作者  fancy
 * @邮箱  fancydeepin@yeah.net
 * @日期  2012-8-25 <p>
 * -----------------------------------------
 
 */ 
public  class Child  extends Base<Child>{ 



从上面代码来看,Base 的泛型类型参数就是 Base 的子类本身,这样一来,当使用 Base 类的子类 Child 类时,Base 类就能准确的获取到当前实际运行的类的 Class,来看下怎么使用


package test; 
/** 
 * -----------------------------------------
 * @描述  测试类
 * @作者  fancy
 * @邮箱  fancydeepin@yeah.net
 * @日期  2012-8-25 <p>
 * -----------------------------------------
 
 */ 
public  class Test { 

     public  static  void main(String[] args){ 
         
        Child child =  new Child(); 
        System.out.println(child.getEntityClass()); 
        System.out.println(child.getEntityName()); 
        System.out.println(child.getEntitySimpleName()); 
    } 
     


后台打印输出的结果:

class test.Child 
test.Child 
Child 

好了,文章接近尾声了,如果你能理解透这个例子,你可以将这个思想运用到 DAO 层面上来,以 Base 类作为所有 DAO 实现类的基类,在 Base 类里面实现数据库的 CURD 等基本

操作,然后再使所有具体的 DAO 类来实现这个基类,那么,实现这个基类的所有的具体的 DAO 都不必再实现数据库的 CURD 等基本操作了,这无疑是一个很棒的做法。


此文章对泛型类的介绍和使用都比较的棒,故转载以后日后温故和学习;

转载自:http://my.oschina.net/leejun2005/blog/74552,对此作者表示感谢!