转载:http://www.gd-emb.org/detail/id-33870.html
摘要:反射和泛型的功能都十分强大,将它们结合起来也特别有趣,本文试图就Java语言的反射和泛型的关系做一点探讨,用以抛砖引玉,引起大家对两种技术的兴趣!
研究泛型与反射之间的关系非常有趣。
我们知道,反射和泛型都是Java的一种动态技术。而不像继承和多态,是面向对象的技术。可以这么说,反射和泛型都像是为了弥补像继承和多态这些面向对象技术的不足而产生的。模式多是建立在面向对象技术基础上的,因而每种模式多多少少在动态性,或者说扩展性方面有些不足,我们就又结合了反射和泛型对模式进行一定的扩展,使它在动态性方面更符合我们的要求。
在将这些技术结合起来的过程中,我们多多少少会想到将泛型和反射结合起来。这是非常有趣的话题:范型和反射都使得Java有了一定的扩展性,但同时都有它自己的不足,而将这两种技术结合起来,是不是又能解决各自的不足,使得它们在动态性上更上一层楼呢?
正像前面所说的,泛型和反射可以相互促进,我们先来看看泛型是怎么帮助反射的。
我们知道,使用反射的一个最大的烦恼就是应用反射以后得到的基本上都是Object类型的对象,这种对象要使用,需要我们进行强制类型转化。而我们更知道,泛型的功能之一就是消除我们使用强制类型转化。
1. 运行期内初始化对象
运行期内初始化对象是我们最常用的反射功能,但是我们通过反射在运行期内得到的对象的类型通常是Object类型的,而这个对象又需要我们在使用的时候进行强制类型转化。现在,有了反射,可以使我们不需要做强制类型转化这个工作。
假设我们已经有了两个类:
我们需要在运行期内初始化这两个对象,我们可以设计如下的初始化方法:
在这个方法里,我们其实是利用泛型在初始化方法里提前做了强制类型转化的工作,这样使得我们不必在使用的时候进行强制类型转化的工作。
它们的测试代码如下:
测试结果为:
cls1...
cls2...
需要注意的是,使用这种方法有几个问题:
第一, return (U) cls.newInstance();这个语句会有警告性的错误,好在这种错误不是编译性的错误,还是我们可以承受的。
第二, 编译器不能做类型检查,如Cls1 i1 = Factory.getInstance("fanxing.factory.dynaFactory.Cls1");这句,换成Cls2 i1 = Factory.getInstance("fanxing.factory.dynaFactory.Cls1");也是可以编译过去的。只是在运行的时候才会出错。
除了上面的方法,还有一种更好的方法。
这个方法需要我们在运行期内传入的对象是Class对象,当然是经过泛型的Class对象,就像下面的样子:
可以很清楚地看到,这里完全没有了强制类型转化,当然也就没有第一种方法所出现的那两个问题。
运行结果为:
cls1...
根据这个方法,我们可以把前面的初始化方法改造为:
我们来进行以下测试:
Cls1 c1 = Factory.getInstance(Cls1.class);
c1.do1();
测试结果为:
cls1...
这时候,如果我们将上面的测试代码改为:
Cls2 c1 = Factory.getInstance(Cls1.class);
就会出现编译错误,可以看到,我们的第二种方法的确避免了第一种方法的弱点,但第一种方法的好处是,只需要往初始化方法里输入类名作为参数。
2. 运行期内调用方法
使用过反射的人都知道,运行期内调用方法后得到的结果也将是一个Object对象。这样的结果在一般的方法反射上也就是可以忍受的了。但是有时候也是不能忍受的,比如,我们想反射List<T>类的iterator()方法。
一般的,我们使用反射可以这么做:
当然,这里返回的是一个Object对象,而List<T>类的iterator()的实际返回类型为T。很明显,我们可以将上面的代码做如下的修改:
同样,我们的代码也会遇到警告性的错误。但是我们可以置之不理。
3. 运行期内初始化数组对象
同样,在运行期内初始化得到的数组也是一个Object对象。就像下面的样子:
Object o = Array.newInstance(int.class,10);
如果我想在getArray()方法里得到数组对象,就会得到像下面这个样子的代码:
这显然不会令我们满意。因为如果我们输入一个Class<T>类型的参数,就希望返回一个T类型的结果。
在这种想法下,我们就使用泛型将getArray()方法做如下修改:
这样的修改,将使我们得到一个比较满意的结果。同样,上面的代码也会得到一个警告性的错误。但是,使用泛型使我们的结果得到了很大的优化。
上面的几个例子让我们看到了泛型能够帮助反射,而且是大大优化了反射的结果。但在同时,反射也不是被动的接受泛型的帮助,反射同样也可以帮助泛型。这是基于反射的基本工作原理:得到数据的元数据。也就是说,可以通过反射得到泛型的元数据。除此之外,反射也有利于帮助泛型数据运行期内初始化。
下面以一两个简单例子加以说明。
4. 使用反射初始化泛型类
我们通常会在一个类里这样使用泛型:
这当然是我们最基本的用法,但常常会这样:编译器希望知道更多的关于这个未知对象如A fst的信息,这样,我们可以在运行期内调用某一些方法。大家说啊,这很容易啊,我们可以把这种未知类型作为参数输入。呵呵,这就对了,有了这样参数,下一步,我们就要使用反射在运行期内调用它的方法。
关于这样的例子,我在这里不再给出。我在这里给出一个简单一些的例子,就是对泛型类初始化需要调用的构造器。
对于上面的Pair<A,B>类,如果构造器的输入参数的类型不是A和B,而是Class<A>和Class<B>,那么我们就不得不在构造器里使用反射了。
由此可见,对于泛型里的未知类型参数,我们也完全可以和普通类型一样使用反射工具。即可以通过反射对这些未知类型参数做反射所能做到的任何事情。
5. 使用反射得到泛型信息
关于这一个小结的问题,显得更加得有趣。
我们还是以上面的Pair<A,B>作为例子,假如我们在一个类中使用到了这个类,如下所示:
如果我们对PairUser类应用反射,我们可以很轻松的得到该类的属性pair的一些信息,如它是private还是public的,它的类型等等。
如果我们通过反射得到pair属性的类型为Pair以后,我们知道该类是一个泛型类,那么我们就想进一步知道该泛型类的更进一步的信息。比如,泛型类的类名,泛型参数的信息等等。
具体到上面的PairUser类的例子,我现在想知道它的属性pair的一些信息,比如,它的类型名、泛型参数的类型,如String和List等等这些信息。所要做的工作如下:
首先是取得这个属性
Field field = PairUser.class.getDeclaredField("pair");
然后是取得属性的泛型类型
Type gType = field.getGenericType();
再判断gType是否为ParameterizedType类型,如果是则转化为ParameterizedType类型的变量
ParameterizedType pType = (ParameterizedType)gType;
取得原始类型
Type rType = pType.getRawType();
然后就可以通过rType.getClass().getName()获得属性pair的类型名。
最后获取参数信息
Type[] tArgs = pType.getActualTypeArguments();
可以通过tArgs[j].getClass().getName()取得属性pair的泛型参数的类型名。
完整的代码如下:
输出结果为:
rawType is instance of java.lang.Class
(class Pair)
actual type arguments are:
instance of java.lang.Class:
(class java.lang.String)
instance of java.lang.Class:
(interface java.util.List)
摘要:反射和泛型的功能都十分强大,将它们结合起来也特别有趣,本文试图就Java语言的反射和泛型的关系做一点探讨,用以抛砖引玉,引起大家对两种技术的兴趣!
研究泛型与反射之间的关系非常有趣。
我们知道,反射和泛型都是Java的一种动态技术。而不像继承和多态,是面向对象的技术。可以这么说,反射和泛型都像是为了弥补像继承和多态这些面向对象技术的不足而产生的。模式多是建立在面向对象技术基础上的,因而每种模式多多少少在动态性,或者说扩展性方面有些不足,我们就又结合了反射和泛型对模式进行一定的扩展,使它在动态性方面更符合我们的要求。
在将这些技术结合起来的过程中,我们多多少少会想到将泛型和反射结合起来。这是非常有趣的话题:范型和反射都使得Java有了一定的扩展性,但同时都有它自己的不足,而将这两种技术结合起来,是不是又能解决各自的不足,使得它们在动态性上更上一层楼呢?
正像前面所说的,泛型和反射可以相互促进,我们先来看看泛型是怎么帮助反射的。
我们知道,使用反射的一个最大的烦恼就是应用反射以后得到的基本上都是Object类型的对象,这种对象要使用,需要我们进行强制类型转化。而我们更知道,泛型的功能之一就是消除我们使用强制类型转化。
1. 运行期内初始化对象
运行期内初始化对象是我们最常用的反射功能,但是我们通过反射在运行期内得到的对象的类型通常是Object类型的,而这个对象又需要我们在使用的时候进行强制类型转化。现在,有了反射,可以使我们不需要做强制类型转化这个工作。
假设我们已经有了两个类:
public class Cls1 { public void do1() { // TODO Auto-generated method stub System.out.println("cls1..."); } } public class Cls2 { public void do2() { // TODO Auto-generated method stub System.out.println("cls2..."); } }
我们需要在运行期内初始化这两个对象,我们可以设计如下的初始化方法:
public class Factory{ public static <U extends Object>U getInstance(String clsName) { try { Class<?> cls = Class.forName(clsName); return (U) cls.newInstance(); } catch(Exception e) { e.printStackTrace(); return null; } } }
在这个方法里,我们其实是利用泛型在初始化方法里提前做了强制类型转化的工作,这样使得我们不必在使用的时候进行强制类型转化的工作。
它们的测试代码如下:
Cls1 i1 = Factory.getInstance("fanxing.factory.dynaFactory.Cls1"); i1.do1(); Cls2 i2 = Factory.getInstance("fanxing.factory.dynaFactory.Cls2"); i2.do2()
测试结果为:
cls1...
cls2...
需要注意的是,使用这种方法有几个问题:
第一, return (U) cls.newInstance();这个语句会有警告性的错误,好在这种错误不是编译性的错误,还是我们可以承受的。
第二, 编译器不能做类型检查,如Cls1 i1 = Factory.getInstance("fanxing.factory.dynaFactory.Cls1");这句,换成Cls2 i1 = Factory.getInstance("fanxing.factory.dynaFactory.Cls1");也是可以编译过去的。只是在运行的时候才会出错。
除了上面的方法,还有一种更好的方法。
这个方法需要我们在运行期内传入的对象是Class对象,当然是经过泛型的Class对象,就像下面的样子:
Class<Cls1> cls = Cls1.class; try { Intf1 obj = cls.newInstance(); obj.do1(); } catch(Exception e) { e.printStackTrace(); }
可以很清楚地看到,这里完全没有了强制类型转化,当然也就没有第一种方法所出现的那两个问题。
运行结果为:
cls1...
根据这个方法,我们可以把前面的初始化方法改造为:
public static <U extends Object> U getInstance(Class<U> cls) { try { return cls.newInstance(); } catch(Exception e) { e.printStackTrace(); return null; } }
我们来进行以下测试:
Cls1 c1 = Factory.getInstance(Cls1.class);
c1.do1();
测试结果为:
cls1...
这时候,如果我们将上面的测试代码改为:
Cls2 c1 = Factory.getInstance(Cls1.class);
就会出现编译错误,可以看到,我们的第二种方法的确避免了第一种方法的弱点,但第一种方法的好处是,只需要往初始化方法里输入类名作为参数。
2. 运行期内调用方法
使用过反射的人都知道,运行期内调用方法后得到的结果也将是一个Object对象。这样的结果在一般的方法反射上也就是可以忍受的了。但是有时候也是不能忍受的,比如,我们想反射List<T>类的iterator()方法。
一般的,我们使用反射可以这么做:
try { Class<?> cls = Class.forName("java.util.ArrayList"); Method m = cls.getDeclaredMethod("iterator",new Class[0]); return m.invoke(this,new Object[0]); } catch(Exception e) { e.printStackTrace(); return null; }
当然,这里返回的是一个Object对象,而List<T>类的iterator()的实际返回类型为T。很明显,我们可以将上面的代码做如下的修改:
try { Class<?> cls = Class.forName("java.util.ArrayList"); Method m = cls.getDeclaredMethod("iterator",new Class[0]); return (T)m.invoke(this,new Object[0]); } catch(Exception e) { e.printStackTrace(); return null; }
同样,我们的代码也会遇到警告性的错误。但是我们可以置之不理。
3. 运行期内初始化数组对象
同样,在运行期内初始化得到的数组也是一个Object对象。就像下面的样子:
Object o = Array.newInstance(int.class,10);
如果我想在getArray()方法里得到数组对象,就会得到像下面这个样子的代码:
public Object getArray(Class cls,int size) { return Array.newInstance(cls,size); }
这显然不会令我们满意。因为如果我们输入一个Class<T>类型的参数,就希望返回一个T类型的结果。
在这种想法下,我们就使用泛型将getArray()方法做如下修改:
public T[] getArray(Class<T> cls,int size) { return (T[])Array.newInstance(cls,size); }
这样的修改,将使我们得到一个比较满意的结果。同样,上面的代码也会得到一个警告性的错误。但是,使用泛型使我们的结果得到了很大的优化。
上面的几个例子让我们看到了泛型能够帮助反射,而且是大大优化了反射的结果。但在同时,反射也不是被动的接受泛型的帮助,反射同样也可以帮助泛型。这是基于反射的基本工作原理:得到数据的元数据。也就是说,可以通过反射得到泛型的元数据。除此之外,反射也有利于帮助泛型数据运行期内初始化。
下面以一两个简单例子加以说明。
4. 使用反射初始化泛型类
我们通常会在一个类里这样使用泛型:
public final class Pair<A,B> { public final A fst; public final B snd; public Pair(A fst, B snd) { this.fst = fst; this.snd = snd; } …… }
这当然是我们最基本的用法,但常常会这样:编译器希望知道更多的关于这个未知对象如A fst的信息,这样,我们可以在运行期内调用某一些方法。大家说啊,这很容易啊,我们可以把这种未知类型作为参数输入。呵呵,这就对了,有了这样参数,下一步,我们就要使用反射在运行期内调用它的方法。
关于这样的例子,我在这里不再给出。我在这里给出一个简单一些的例子,就是对泛型类初始化需要调用的构造器。
对于上面的Pair<A,B>类,如果构造器的输入参数的类型不是A和B,而是Class<A>和Class<B>,那么我们就不得不在构造器里使用反射了。
public Pair(Class<A> typeA, Class<B> typeB) { this.fst = typeA.newInstance(); this.snd = typeB.newInstance(); …… }
由此可见,对于泛型里的未知类型参数,我们也完全可以和普通类型一样使用反射工具。即可以通过反射对这些未知类型参数做反射所能做到的任何事情。
5. 使用反射得到泛型信息
关于这一个小结的问题,显得更加得有趣。
我们还是以上面的Pair<A,B>作为例子,假如我们在一个类中使用到了这个类,如下所示:
public Class PairUser { private Pair<String,List> pair; …… }
如果我们对PairUser类应用反射,我们可以很轻松的得到该类的属性pair的一些信息,如它是private还是public的,它的类型等等。
如果我们通过反射得到pair属性的类型为Pair以后,我们知道该类是一个泛型类,那么我们就想进一步知道该泛型类的更进一步的信息。比如,泛型类的类名,泛型参数的信息等等。
具体到上面的PairUser类的例子,我现在想知道它的属性pair的一些信息,比如,它的类型名、泛型参数的类型,如String和List等等这些信息。所要做的工作如下:
首先是取得这个属性
Field field = PairUser.class.getDeclaredField("pair");
然后是取得属性的泛型类型
Type gType = field.getGenericType();
再判断gType是否为ParameterizedType类型,如果是则转化为ParameterizedType类型的变量
ParameterizedType pType = (ParameterizedType)gType;
取得原始类型
Type rType = pType.getRawType();
然后就可以通过rType.getClass().getName()获得属性pair的类型名。
最后获取参数信息
Type[] tArgs = pType.getActualTypeArguments();
可以通过tArgs[j].getClass().getName()取得属性pair的泛型参数的类型名。
完整的代码如下:
try { Field field = PairUser.class.getDeclaredField("pair"); Type gType = field.getGenericType(); if(gType instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType)gType; Type rType = pType.getRawType(); System.out.println("rawType is instance of " + rType.getClass().getName()); System.out.println(" (" + rType + ")"); Type[] tArgs = pType.getActualTypeArguments(); System.out.println("actual type arguments are:"); for (int j = 0; j < tArgs.length; j++) { System.out.println(" instance of " + tArgs[j].getClass().getName() + ":"); System.out.println(" (" + tArgs[j] + ")"); } } else { System.out.println("getGenericType is not a ParameterizedType!"); } } catch(Exception e) { e.printStackTrace(); } }
输出结果为:
rawType is instance of java.lang.Class
(class Pair)
actual type arguments are:
instance of java.lang.Class:
(class java.lang.String)
instance of java.lang.Class:
(interface java.util.List)
发表评论
-
动态ClassLoader
2013-03-21 22:29 802代码来源 http://www.oschina.net/co ... -
JAVA TIMER用法学习
2011-12-29 18:53 1060JAVA TIMER用法学习 引用http://www.blo ... -
svn版本文件删除
2010-04-08 04:23 828转自:http://www.subversion.org.cn ... -
java运算符
2009-12-30 11:22 1126【转自】http://blog.csdn.net/lengch ... -
jre 环境配置
2009-11-25 15:08 5356CLASSPATH=.;D:\program files\jd ... -
LDAP一些网站资料
2009-11-14 12:21 1196LDAP技术资源 转自http://blog.csdn.net ... -
sturts2中sturts.xml中result中的 type 含义
2009-10-09 17:12 1667转自:http://tieba.baidu.com/f?kz= ... -
配置支持SSL的Tomcat
2009-09-29 11:51 1133转自http://www.wangchao.net.c ... -
struts2中的常用标签
2009-09-21 19:38 8201. Struts2页面开发中常用标签使用说明 1.1.往a ... -
java.lang.UnsupportedClassVersionError: Bad version number in .class file异常
2009-09-19 20:40 1972转自http://hi.baidu.com/haihe118/ ... -
JDBC连接__数据库
2009-08-24 12:04 8181.Access Class.forName(" ... -
org.jboss.xb.binding.JBossXBRuntimeException
2009-08-21 10:39 3219org.jboss.xb.binding.JBossXBR ...
相关推荐
pandas whl安装包,对应各个python版本和系统(具体看资源名字),找准自己对应的下载即可! 下载后解压出来是已.whl为后缀的安装包,进入终端,直接pip install pandas-xxx.whl即可,非常方便。 再也不用担心pip联网下载网络超时,各种安装不成功的问题。
基于java的大学生兼职信息系统答辩PPT.pptx
基于java的乐校园二手书交易管理系统答辩PPT.pptx
tornado-6.4-cp38-abi3-musllinux_1_1_i686.whl
Android Studio Ladybug 2024.2.1(android-studio-2024.2.1.10-mac.dmg)适用于macOS Intel系统,文件使用360压缩软件分割成两个压缩包,必须一起下载使用: part1: https://download.csdn.net/download/weixin_43800734/89954174 part2: https://download.csdn.net/download/weixin_43800734/89954175
有学生和教师两种角色 登录和注册模块 考场信息模块 考试信息模块 点我收藏 功能 监考安排模块 考场类型模块 系统公告模块 个人中心模块: 1、修改个人信息,可以上传图片 2、我的收藏列表 账号管理模块 服务模块 eclipse或者idea 均可以运行 jdk1.8 apache-maven-3.6 mysql5.7及以上 tomcat 8.0及以上版本
tornado-6.1b2-cp38-cp38-macosx_10_9_x86_64.whl
Android Studio Ladybug 2024.2.1(android-studio-2024.2.1.10-mac.dmg)适用于macOS Intel系统,文件使用360压缩软件分割成两个压缩包,必须一起下载使用: part1: https://download.csdn.net/download/weixin_43800734/89954174 part2: https://download.csdn.net/download/weixin_43800734/89954175
matlab
基于java的毕业生就业信息管理系统答辩PPT.pptx
随着高等教育的普及和毕业设计的日益重要,为了方便教师、学生和管理员进行毕业设计的选题和管理,我们开发了这款基于Web的毕业设计选题系统。 该系统主要包括教师管理、院系管理、学生管理等多个模块。在教师管理模块中,管理员可以新增、删除教师信息,并查看教师的详细资料,方便进行教师资源的分配和管理。院系管理模块则允许管理员对各个院系的信息进行管理和维护,确保信息的准确性和完整性。 学生管理模块是系统的核心之一,它提供了学生选题、任务书管理、开题报告管理、开题成绩管理等功能。学生可以在此模块中进行毕业设计的选题,并上传任务书和开题报告,管理员和教师则可以对学生的报告进行审阅和评分。 此外,系统还具备课题分类管理和课题信息管理功能,方便对毕业设计课题进行分类和归档,提高管理效率。在线留言功能则为学生、教师和管理员提供了一个交流互动的平台,可以就毕业设计相关问题进行讨论和解答。 整个系统设计简洁明了,操作便捷,大大提高了毕业设计的选题和管理效率,为高等教育的发展做出了积极贡献。
这个数据集来自世界卫生组织(WHO),包含了2000年至2015年期间193个国家的预期寿命和相关健康因素的数据。它提供了一个全面的视角,用于分析影响全球人口预期寿命的多种因素。数据集涵盖了从婴儿死亡率、GDP、BMI到免疫接种覆盖率等多个维度,为研究者提供了丰富的信息来探索和预测预期寿命。 该数据集的特点在于其跨国家的比较性,使得研究者能够识别出不同国家之间预期寿命的差异,并分析这些差异背后的原因。数据集包含22个特征列和2938行数据,涉及的变量被分为几个大类:免疫相关因素、死亡因素、经济因素和社会因素。这些数据不仅有助于了解全球健康趋势,还可以辅助制定公共卫生政策和社会福利计划。 数据集的处理包括对缺失值的处理、数据类型转换以及去重等步骤,以确保数据的准确性和可靠性。研究者可以使用这个数据集来探索如教育、健康习惯、生活方式等因素如何影响人们的寿命,以及不同国家的经济发展水平如何与预期寿命相关联。此外,数据集还可以用于预测模型的构建,通过回归分析等统计方法来预测预期寿命。 总的来说,这个数据集是研究全球健康和预期寿命变化的宝贵资源,它不仅提供了历史数据,还为未来的研究和政策制
基于微信小程序的高校毕业论文管理系统小程序答辩PPT.pptx
基于java的超市 Pos 收银管理系统答辩PPT.pptx
基于java的网上报名系统答辩PPT.pptx
基于java的网上书城答辩PPT.pptx
婚恋网站 SSM毕业设计 附带论文 启动教程:https://www.bilibili.com/video/BV1GK1iYyE2B
基于java的戒烟网站答辩PPT.pptx
基于微信小程序的“健康早知道”微信小程序答辩PPT.pptx
Capital Bikeshare 数据集是一个包含从2020年5月到2024年8月的自行车共享使用情况的数据集。这个数据集记录了华盛顿特区Capital Bikeshare项目中自行车的租赁模式,包括了骑行的持续时间、开始和结束日期时间、起始和结束站点、使用的自行车编号、用户类型(注册会员或临时用户)等信息。这些数据可以帮助分析和预测自行车共享系统的需求模式,以及了解用户行为和偏好。 数据集的特点包括: 时间范围:覆盖了四年多的时间,提供了长期的数据观察。 细节丰富:包含了每次骑行的详细信息,如日期、时间、天气条件、季节等,有助于深入分析。 用户分类:数据中区分了注册用户和临时用户,可以分析不同用户群体的使用习惯。 天气和季节因素:包含了天气情况和季节信息,可以研究这些因素对骑行需求的影响。 通过分析这个数据集,可以得出关于自行车共享使用模式的多种见解,比如一天中不同时间段的使用高峰、不同天气条件下的使用差异、季节性变化对骑行需求的影响等。这些信息对于城市规划者、交通管理者以及自行车共享服务提供商来说都是非常宝贵的,可以帮助他们优化服务、提高效率和满足用户需求。同时,这个数据集也