泛型与反射

来源:互联网 发布:网络交换机套什么定额 编辑:程序博客网 时间:2024/05/16 09:08
                                                                        泛型与反射
 
研究泛型与反射之间的关系非常有趣。
我们知道,反射和泛型都是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 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 cls = Cls1.class;
        try
        {
               Intf1 obj = cls.newInstance();
               obj.do1();
        }
        catch(Exception e)
        {
               e.printStackTrace();
        }
可以很清楚地看到,这里完全没有了强制类型转化,当然也就没有第一种方法所出现的那两个问题。
运行结果为:
cls1...
根据这个方法,我们可以把前面的初始化方法改造为:
public static U getInstance(Class 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类的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类的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类型的结果。
在这种想法下,我们就使用泛型将getArray()方法做如下修改:
public T[] getArray(Class cls,int size)
{
        return (T[])Array.newInstance(cls,size);
}
这样的修改,将使我们得到一个比较满意的结果。同样,上面的代码也会得到一个警告性的错误。但是,使用泛型使我们的结果得到了很大的优化。
 
上面的几个例子让我们看到了泛型能够帮助反射,而且是大大优化了反射的结果。但在同时,反射也不是被动的接受泛型的帮助,反射同样也可以帮助泛型。这是基于反射的基本工作原理:得到数据的元数据。也就是说,可以通过反射得到泛型的元数据。除此之外,反射也有利于帮助泛型数据运行期内初始化。
下面以一两个简单例子加以说明。
 
4.  使用反射初始化泛型类
我们通常会在一个类里这样使用泛型:
public final class Pair {
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,而是Class和Class,那么我们就不得不在构造器里使用反射了。
 


原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 已充值成功送朋友话费不能送怎么办 微信绑定银行卡被盗刷q币怎么办 微信红包过了24小时没退回怎么办 微信6.67版本红包发错了怎么办 苹果6s还原后激活出错怎么办 q币充给了不存在的账号怎么办 怎么办微信的钱换成淘宝币 学信网密码密保手机号都忘了怎么办 第五人格玩游戏时总是闪退怎么办 qq安全中心密保手机换了怎么办 微信冻结账号绑定了银行卡怎么办 扣扣红包密码是支付密码忘了怎么办 红包退回通知不小心删除了怎么办 QQ炫舞金币充错账号怎么办 晋江的小说用晋江币买不了是怎么办 中国银行储蓄不能充值支付宝怎么办 微信怎么改银行卡密码忘记了怎么办 微信号手机号码换了密码忘了怎么办 微信被盗密码被改绑定手机号怎么办 微信qq号登陆改密码忘记了怎么办 本人微信红包赌博输了50万怎么办 4g飞享套餐话费用完了怎么办 手机丢了查话费欠了几百块怎么办 注销电信手机卡里面的余额怎么办 联通手机卡注销后里面的余额怎么办 手机卡网上销户以后剩余话费怎么办 联通新号注册微信发不了短信怎么办 韩博士装机卡在驱动恢复怎么办 xp打印后程序服务没有运行怎么办 刚注册的微信显示异常怎么办 不小心删了照片怎么办不要钱 qq邀请好友辅助验证成功后怎么办 微信申诉怎么让好友发验证码怎么办 微信申诉好友都删除了怎么办 恋与制作人原来的帐号不见了怎么办 手机号被别人注册了手机银行怎么办 想上老婆的陌陌号但要验证码怎么办 中国家医居民端注册信息有误怎么办 别人给我充的q币怎么办 qq忘记密码手机号码也换了怎么办 手机号码不用了微信忘记密码怎么办