泛型的好处:
泛型的主要好处就是让编译器保留参数的类型信息,执行类型检查,执行类型转换(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 [ 随笔均原创,转载请注明出处:http://www./fancydeepin ] 评论# re: java 泛型 深入 回复 更多评论2012-08-26 15:34 by fancydeepin回复 @Kane:
这个问题问的好!我也跟你有一样的疑惑,当时我立即去查 java.lang.Class 的源码,找到 getGenericSuperclass() 的实现: public Type getGenericSuperclass() { if (getGenericSignature() != null) { // Historical irregularity: // Generic signature marks interfaces with superclass = Object // but this API returns null for interfaces if (isInterface()) return null; return getGenericInfo().getSuperclass(); } else return getSuperclass(); } 但是以上实现中的 getGenericSignature 和 getSuperclass 两个方法都是用 native 声明的本地方法,找不到它们的源码,没有收获。 文章中的 Base 类和 Child 类在编译完成后的字节码文件中,你通过反编译来看,真真切切是没有保留泛型信息,编译期间泛型被擦除: 在文章的代码中,如果想输出查看一下type的类型: …… Type type = clazz.getGenericSuperclass(); System.out.println(type); …… 打印确实能够输出我们想要的:test.Base<test.Child> getGenericSuperclass() 具体是怎么取到泛型的信息的我也不太清楚,只是知道用这个方法能取得到,就我知道的,想通过反射取得泛型信息有三种方法,一种就是文章中提到的,一种是变量,另外一种就是通过把泛型用做一个方法的参数类型,方法可以保留参数的相关信息,这样就可以用反射来先获取方法的信息,然后再进一步获取泛型参数的相关信息,这样也能得到泛型的实际参数类型。 |
|