`

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泛型的用法及T.class的获取过程解析

    Java泛型的用法及T.class的获取过程解析 Java泛型是Java编程语言中的一种重要特性,它允许开发者在编写代码时指定类型参数,从而提高代码的灵活性和可读性。本文将详细介绍Java泛型的用法 及T.class的获取过程解析...

    Java泛型三篇文章,让你彻底理解泛型(super ,extend等区别)

    Java 泛型详解 Java 泛型是 Java SE 5.0 中引入的一项特征,它允许程序员在编译时检查类型安全,从而减少了 runtime 错误的可能性。泛型的主要优点是可以Reusable Code,让程序员编写更加灵活和可维护的代码。 ...

    Java泛型应用实例

    Java泛型是Java编程语言中的一个强大特性,它允许我们在定义类、接口和方法时指定类型参数,从而实现代码的重用和类型安全。在Java泛型应用实例中,我们可以看到泛型如何帮助我们提高代码的灵活性和效率,减少运行时...

    很好的Java泛型的总结

    Java泛型机制详解 Java泛型是Java语言中的一种机制,用于在编译期检查类型安全。Java泛型的出现解决了Java早期版本中类型安全检查的缺陷。Java泛型的好处是可以在编译期检查类型安全,避免了运行时的...

    java 泛型类的类型识别示例

    综上所述,虽然Java泛型在编译后会进行类型擦除,但通过上述技巧,我们仍然能够在运行时获得关于泛型类实例化类型的一些信息。在实际开发中,这些方法可以帮助我们编写更加灵活和安全的代码。在示例文件`GenericRTTI...

    java泛型技术之发展

    Java泛型是Java编程语言中的一个关键特性,它在2004年随着Java SE 5.0的发布而引入,极大地增强了代码的类型安全性和重用性。本篇文章将深入探讨Java泛型的发展历程、核心概念以及其在实际开发中的应用。 1. **发展...

    SUN公司Java泛型编程文档

    Java泛型是Java编程语言中的一个关键特性,它在2004年随着JDK 5.0的发布被引入。这个特性极大地提高了代码的类型安全性和可读性,减少了在运行时出现ClassCastException的可能性。SUN公司的Java泛型编程文档,包括...

    java 泛型接口示例

    下面我们将详细探讨Java泛型接口的相关知识点。 1. **泛型接口的定义** 泛型接口的定义方式与普通接口类似,只是在接口名之后添加了尖括号`&lt;T&gt;`,其中`T`是一个类型参数,代表某种未知的数据类型。例如: ```java...

    java 泛型方法使用示例

    下面我们将深入探讨Java泛型方法的概念、语法以及使用示例。 **一、泛型方法概念** 泛型方法是一种具有类型参数的方法,这些类型参数可以在方法声明时指定,并在方法体内部使用。与类的泛型类似,它们提供了编译时...

    java泛型的内部原理及更深应用

    Java泛型是Java编程语言中的一个强大特性,它允许在定义类、接口和方法时使用类型参数,从而实现参数化类型。这使得代码更加安全、可读性更强,并且能够减少类型转换的必要。在“java泛型的内部原理及更深应用”这个...

    JAVA泛型加减乘除

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

    Java 泛型擦除后的三种补救方法

    Java 泛型是一种强大的工具,它允许我们在编程时指定变量的类型,提供了编译时的类型安全。然而,Java 的泛型在运行时是被擦除的,这意味着在运行时刻,所有的泛型类型信息都会丢失,无法直接用来创建对象或进行类型...

    java泛型学习ppt

    "Java 泛型学习" Java 泛型是 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的...

    Java泛型使用详细分析.pdf

    Java 泛型使用详细分析 Java 泛型是 Java 语言中的一种类型系统特性,允许开发者在编译期检查类型安全,以避免在运行时出现类型相关的错误。在本文中,我们将详细介绍 Java 泛型的使用方法和实现原理。 一、泛型的...

    Java泛型技术之发展.pdf

    Java泛型是Java编程语言中的一个关键特性,它在2004年随着Java SE 5.0的发布而引入,极大地增强了代码的类型安全性和重用性。本篇文章将深入探讨Java泛型的发展历程、核心概念以及其在实际开发中的应用。 1. **发展...

    面试必须资料java泛型攻略、

    #### 一、什么是Java泛型? Java泛型(Generics)是一种在编译时确保类型安全的机制,它允许程序员编写类型安全的通用类或方法,而无需进行显式的类型转换。在Java 1.5引入泛型之前,集合类(如`ArrayList`)只能...

Global site tag (gtag.js) - Google Analytics