`

java 泛型

    博客分类:
  • java
 
阅读更多

 

转载【http://www.blogjava.net/fancydeepin

泛型的好处:

    泛型的主要好处就是让编译器保留参数的类型信息,执行类型检查,执行类型转换(casting)操作,编译器保证了这些类型转换(casting)的绝对无误。


        /******* 不使用泛型类型 *******/
        List list1 = new ArrayList();
        list1.add(8080);                                  //编译器不检查值
        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
        


在以上代码中定义的 List<String> 和 List<Integer> 等类型,在编译之后都会变成 List,而由泛型附加的类型信息对 JVM 来说是不可见的,所以第一条打印语句输出 true,

第二、第三条打印语句都输出 java.util.ArrayList,这都说明 List<String> 和 List<Integer> 的对象使用的都是同一份字节码,运行期间并不存在泛型。

来看一个简单的例子:


package test;

import java.util.List;
/**
 * -----------------------------------------
 * @描述  类型擦除
 * @作者  fancy
 * @邮箱  fancydeepin@yeah.net
 * @日期  2012-8-25 <p>
 * -----------------------------------------
 
*/
public class GenericsApp {

    
    public void method(List<String> list){
        
    }
    
    /*
     * 编译出错,这两个方法不属于重载,由于类型的擦除,使得这两个方法的参数列表的参数均为List类型,
     * 这就相当于同一个方法被声明了两次,编译自然无法通过了
     * 
    public void method(List<Integer> list){
        
    }
    
*/
    
}


以此类为例,在 cmd 中 编译 GenericsApp.java 得到字节码,然后再反编译这份字节码:



从图中可以看出,经反编译后的源码中 method 方法的参数变成了 List 类型,说明泛型的类型被擦除了,字节码文件中不存在泛型,也就是说,运行期间泛型并不存在,它在

编译完成之后就已经被擦除了。


泛型类型的子类型:

    泛型类型跟其是否是泛型类型的子类型没有任何关系。


        List<Object> list1;
        List<String> list2;
        
        list1 = list2; // 编译出错
        list2 = list1; // 编译出错


在 Java 中,Object 类是所有类的超类,自然而然的 Object 类是 String 类的超类,按理,将一个 String 类型的对象赋值给一个 Object 类型的对象是可行的,

但是泛型中并不存在这样的逻辑,泛型类型跟其是否子类型没有任何关系。


泛型中的通配符(?):

    由于泛型类型与其子类型存在不相关性,那么在不能确定泛型类型的时候,可以使用通配符(?),通配符(?)能匹配任意类型。


        List<?> list;
        List<Object> list1 = null;
        List<String>  list2 = null;
        
        list = list1;
        list = list2;



限定通配符的上界:


        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 的实际参数类型。


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 等基本操作了,这无疑是一个很棒的做法。


(通过反射获得泛型的实际类型参数)补充:

泛型反射的关键是获取 ParameterizedType 接口,再调用 ParameterizedType 接口中的 getActualTypeArguments() 方法就可获得实际绑定的类型。

由于去参数化(擦拭法),也只有在 超类(调用 getGenericSuperclass 方法) 或者成员变量(调用 getGenericType 方法)或者方法(调用 getGenericParameterTypes 方法)

像这些有方法返回 ParameterizedType 类型的时候才能反射成功。

上面只谈到超类如何反射,下面将变量和方法的两种反射补上:

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


package test;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;

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

    public static void main(String[] args){
        /**
         * 泛型编译后会去参数化(擦拭法),因此无法直接用反射获取泛型的参数类型
         * 可以把泛型用做一个方法的参数类型,方法可以保留参数的相关信息,这样就可以用反射先获取方法的信息
         * 然后再进一步获取泛型参数的相关信息,这样就得到了泛型的实际参数类型
         
*/
        try {
            Class<?> clazz = Test.class//取得 Class
            Method method = clazz.getDeclaredMethod("applyCollection", Collection.class); //取得方法
            Type[] type = method.getGenericParameterTypes(); //取得泛型类型参数集
            ParameterizedType ptype = (ParameterizedType)type[0];//将其转成参数化类型,因为在方法中泛型是参数,且Number是第一个类型参数
            type = ptype.getActualTypeArguments(); //取得参数的实际类型
            System.out.println(type[0]); //取出第一个元素
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    //声明一个空的方法,并将泛型用做为方法的参数类型
    public void applyCollection(Collection<Number> collection){
        
    }
}


后台打印输出的结果:


class java.lang.Number



通过字段变量,反射获得泛型的实际类型参数:


package test;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;

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

    private Map<String, Number> collection;
    
    public static void main(String[] args){
        
        try {
            
            Class<?> clazz = Test.class//取得 Class
            Field field = clazz.getDeclaredField("collection"); //取得字段变量
            Type type = field.getGenericType(); //取得泛型的类型
            ParameterizedType ptype = (ParameterizedType)type; //转成参数化类型
            System.out.println(ptype.getActualTypeArguments()[0]); //取出第一个参数的实际类型
            System.out.println(ptype.getActualTypeArguments()[1]); //取出第二个参数的实际类型
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
}


后台打印输出的结果:


class java.lang.String
class java.lang.Number
分享到:
评论

相关推荐

    Java泛型编程指南.pdf

    Java泛型编程指南.pdf 此文章译自SUN的泛型编程指南

    Java泛型和集合

    Java Generics and Collections 英文版,详细描述java 泛型技术

    java 泛型类的类型识别示例

    java 泛型类的类型识别示例 java 泛型类的类型识别示例 java 泛型类的类型识别示例

    java 泛型接口示例

    java 泛型接口示例 java 泛型接口示例 java 泛型接口示例

    java 泛型方法使用示例

    java 泛型方法使用示例 java 泛型方法使用示例 java 泛型方法使用示例

    Java泛型的用法及T.class的获取过程解析

    主要介绍了Java泛型的用法及T.class的获取过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

    JAVA泛型加减乘除

    这是一个使用JAVA实现的泛型编程,分为两部分,第一部分创建泛型类,并实例化泛型对象,得出相加结果。 第二部分用户自行输入0--4,选择要进行的加减乘除运算或退出,再输入要进行运算的两个数,并返回运算结果及...

    java泛型技术之发展

    java泛型技术之发展,学习JAVA 泛型的不错东东

    1.java泛型定义.zip

    1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1....

    很好的Java泛型的总结

    很好的Java泛型的总结,看完之后你一定会知道java泛型的底层机制,你一定会学会Java泛型!

    4.java泛型的限制.zip

    4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip...

    java泛型学习ppt

    java,学习java泛型,java培训之泛型.pptxjava培训之泛型.pptxjava培训之泛型.pptxjava培训之泛型.pptx

    java泛型总结

    深入理解java泛型,包括类名泛型的定义,方法泛型定义,泛型的返回

    SUN公司Java泛型编程文档

    Sun公司的Java泛型编程文档,英文原版和网络翻译版,想对泛型有更清楚的认识的朋友可以看看,必定会有所帮助

    java泛型详解.pdf

    java泛型详解.pdf

    JAVA泛型简单排序实例

    JAVA泛型源代码实现以下功能:返回数组元素的最大值/最小值下标;判断数组元素是否按升序排列;T对象数组排序;二分法查找key元素;

    思维导图之Java泛型详解

    思维导图之Java泛型详解

    Java泛型技术之发展

    Java泛型技术之发展

    JAVA泛型教程(帮你解决学习泛型的苦恼)

    JAVA泛型教程(帮你解决学习泛型的苦恼). Java 泛型编程可能会碰到很多问题,本教程可能会对你有帮助哦。

Global site tag (gtag.js) - Google Analytics