42.java编程思想——反射 运行期类信息

42.java编程思想——反射 运行期类信息

如果不知道一个对象的准确类型,RTTI 会帮助我们调查。但却有一个限制:类型必须是在编译期间已知的,否则就不能用RTTI调查它,进而无法展开下一步的工作。换言之,编译器必须明确知道RTTI 要处理的所有类。

从表面看,这似乎并不是一个很大的限制,但假若得到的是一个不在自己程序空间内的对象的句柄,这时又会怎样呢?事实上,对象的类即使在编译期间也不可由我们的程序使用。例如,假设我们从磁盘或者网络获得一系列字节,而且被告知那些字节代表一个类。由于编译器在编译代码时并不知道那个类的情况,所以怎样才能顺利地使用这个类呢?

在传统的程序设计环境中,出现这种情况的概率或许很小。但当我们转移到一个规模更大的编程世界中,却必须对这个问题加以高度重视。第一个要注意的是基于组件的程序设计。在这种环境下,我们用“快速应用开发”(RAD)模型来构建程序项目。RAD 一般是在应用程序构建工具中内建的。这是编制程序的一种可视途径(在屏幕上以窗体的形式出现)。可将代表不同组件的图标拖曳到窗体中。随后,通过设定这些组件的属性或者值,进行正确的配置。设计期间的配置要求任何组件都是可以“例示”的(即可以自由获得它们的实例)。这些组件也要揭示出自己的一部分内容,允许程序员读取和设置各种值。此外,用于控制GUI 事件的组件必须揭示出与相应的方法有关的信息,以便RAD 环境帮助程序员用自己的代码覆盖这些由事件驱动的方法。“反射”提供了一种特殊的机制,可以侦测可用的方法,并产生方法名。通过Java Beans,Java 1.1 为这种基于组件的程序设计提供了一个基础结构。

在运行期查询类信息的另一个原动力是通过网络创建与执行位于远程系统上的对象。这就叫作“远程方法调用”(RMI),它允许Java 程序(版本1.1以上)使用由多台机器发布或分布的对象。这种对象的分布可能是由多方面的原因引起的:可能要做一件计算密集型的工作,想对它进行分割,让处于空闲状态的其他机器分担部分工作,从而加快处理进度。某些情况下,可能需要将用于控制特定类型任务(比如多层客户/服务器架构中的“运作规则”)的代码放置在一台特殊的机器上,使这台机器成为对那些行动进行描述的一个通用储藏所。而且可以方便地修改这个场所,使其对系统内的所有方面产生影响(这是一种特别有用的设计思路,因为机器是独立存在的,所以能轻易修改软件!)。分布式计算也能更充分地发挥某些专用硬件的作用,它们特别擅长执行一些特定的任务——例如矩阵逆转——但对常规编程来说却显得太夸张或者太昂贵了。

在Java 1.1 中,Class 类得到了扩展,可以支持“反射”的概念。针对Field,Method 以及Constructor类(每个都实现了Memberinterface——成员接口),它们都新增了一个库:java.lang.reflect。这些类型的对象都是JVM 在运行期创建的,用于代表未知类里对应的成员。这样便可用构建器创建新对象,用get()和set()方法读取和修改与Field对象关联的字段,以及用invoke()方法调用与Method 对象关联的方法。此外,我们可调用方法getFields(),getMethods(),getConstructors(),分别返回用于表示字段、方法以及构建器的对象数组(在联机文档中,还可找到与Class 类有关的更多的资料)。因此,匿名对象的类信息可在运行期被完整的揭露出来,而在编译期间不需要知道任何东西。

 “反射”并没有什么神奇的地方。通过“反射”同一个未知类型的对象打交道时,JVM只是简单地检查那个对象,并调查它从属于哪个特定的类(就象以前的RTTI 那样)。但在这之后,在我们做其他任何事情之前,Class 对象必须载入。因此,用于那种特定类型的.class 文件必须能由JVM 调用(要么在本地机器内,要么可以通过网络取得)。所以RTTI 和“反射”之间唯一的区别就是对RTTI 来说,编译器会在编译期打开和检查.class 文件。换句话说,我们可以用“普通”方式调用一个对象的所有方法;但对“反射”来说,.class 文件在编译期间是不可使用的,而是由运行期环境打开和检查。

1     一个类方法提取器

很少需要直接使用反射工具;之所以在语言中提供它们,仅仅是为了支持其他Java 特性,比如对象序列化、Java Beans 以及RMI。但是,我们许多时候仍然需要动态提取与一个类

有关的资料。其中特别有用的工具便是一个类方法提取器。正如前面指出的那样,若检视类定义源码或者联机文档,只能看到在那个类定义中被定义或覆盖的方法,基础类那里还有大量资料拿不到。幸运的是,“反射”做到了这一点,可用它写一个简单的工具,令其自动展示整个接口。下面便是具体的程序:

1.1     代码

import java.lang.reflect.*;

public class ShowMethods {

    static finalString usage= "usage: \n" + "ShowMethodsqualified.class.name\n"

            + "To show all methods in class or: \n"+ "ShowMethods qualified.class.nameword\n"

            + "To search for methods involving 'word'";

    public staticvoidmain(String[] args){

        if (args.length< 1) {

            System.out.println(usage);

            System.exit(0);

        }

        try {

            Class c = Class.forName(args[0]);

            Method[] m = c.getMethods();

            Constructor[] ctor = c.getConstructors();

            if (args.length== 1) {

                for (int i = 0; i < m.length; i++)

                    System.out.println(m[i].toString());

                for (int i = 0; i < ctor.length; i++)

                    System.out.println(ctor[i].toString());

            } else {

                for (int i = 0; i < m.length; i++)

                    if (m[i].toString().indexOf(args[1]) != -1)

                        System.out.println(m[i].toString());

                for (int i = 0; i < ctor.length; i++)

                    if (ctor[i].toString().indexOf(args[1]) != -1)

                        System.out.println(ctor[i].toString());

            }

        } catch (ClassNotFoundException e) {

            System.out.println("No such class: "+ e);

        }

    }

} /// :~

1.2     执行

usage:

ShowMethodsqualified.class.name

Toshow all methods in class or:

ShowMethodsqualified.class.name word

Tosearch for methods involving 'word'

加入参数执行后:

publicstatic void main(String[])

publicfinal void wait() throws InterruptedException

publicfinal void wait(long,int) throws InterruptedException

publicfinal native void wait(long) throws InterruptedException

publicboolean equals(Object)

publicString toString()

publicnative int hashCode()

publicfinal native Class getClass()

publicfinal native void notify()

publicfinal native void notifyAll()

publicShowMethodsClean()

Class 方法getMethods()和getConstructors()可以分别返回Method 和Constructor 的一个数组。每个类都提供了进一步的方法,可解析出它们所代表的方法的名字、参数以及返回值。但也可以象这样一样只使用toString(),生成一个含有完整方法签名的字串。代码剩余的部分只是用于提取命令行信息,判断特定的签名是否与我们的目标字串相符(使用indexOf()),并打印出结果。

这里便用到了“反射”技术,因为由Class.forName()产生的结果不能在编译期间获知,所以所有方法签名信息都会在运行期间提取。若研究一下联机文档中关于“反射”(Reflection)的那部分文字,就会发现它已提供了足够多的支持,可对一个编译期完全未知的对象进行实际的设置以及发出方法调用。同样地,这也属于几乎完全不用我们操心的一个步骤——Java 自己会利用这种支持,所以程序设计环境能够控制JavaBeans——但它无论如何都是非常有趣的。

一个有趣的试验是运行java ShowMehodsShowMethods。这样做可得到一个列表,其中包括一个public 默认构建器,尽管我们在代码中看见并没有定义一个构建器。我们看到的是由编译器自动合成的那一个构建器。

如果随之将ShowMethods 设为一个非public 类(即换成“友好”类),合成的默认构建器便不会在输出结果中出现。合成的默认构建器会自动获得与类一样的访问权限。

ShowMethods 的输出仍然有些“不爽”。调用

ShowMethods java.lang.String 得到的输出结果

publicboolean equals(Object)

publicString toString()

publicint hashCode()

publicint compareTo(String)

publicint compareTo(Object)

publicint indexOf(String,int)

publicint indexOf(String)

publicint indexOf(int,int)

publicint indexOf(int)

publicstatic String valueOf(int)

publicstatic String valueOf(long)

publicstatic String valueOf(float)

publicstatic String valueOf(boolean)

publicstatic String valueOf(char[])

publicstatic String valueOf(char[],int,int)

publicstatic String valueOf(Object)

publicstatic String valueOf(char)

publicstatic String valueOf(double)

publicchar charAt(int)

publicint codePointAt(int)

publicint codePointBefore(int)

publicint codePointCount(int,int)

publicint compareToIgnoreCase(String)

publicString concat(String)

publicboolean contains(CharSequence)

publicboolean contentEquals(CharSequence)

publicboolean contentEquals(StringBuffer)

publicstatic String copyValueOf(char[])

publicstatic String copyValueOf(char[],int,int)

publicboolean endsWith(String)

publicboolean equalsIgnoreCase(String)

publicstatic String format(Locale,String,Object[])

publicstatic String format(String,Object[])

publicvoid getBytes(int,int,byte[],int)

publicbyte[] getBytes(Charset)

publicbyte[] getBytes(String) throws UnsupportedEncodingException

publicbyte[] getBytes()

publicvoid getChars(int,int,char[],int)

publicnative String intern()

publicboolean isEmpty()

publicstatic String join(CharSequence,CharSequence[])

publicstatic String join(CharSequence,Iterable)

publicint lastIndexOf(int)

publicint lastIndexOf(String)

publicint lastIndexOf(String,int)

publicint lastIndexOf(int,int)

publicint length()

publicboolean matches(String)

publicint offsetByCodePoints(int,int)

publicboolean regionMatches(int,String,int,int)

publicboolean regionMatches(boolean,int,String,int,int)

publicString replace(char,char)

publicString replace(CharSequence,CharSequence)

publicString replaceAll(String,String)

publicString replaceFirst(String,String)

publicString[] split(String)

publicString[] split(String,int)

publicboolean startsWith(String,int)

publicboolean startsWith(String)

publicCharSequence subSequence(int,int)

publicString substring(int)

publicString substring(int,int)

publicchar[] toCharArray()

publicString toLowerCase(Locale)

publicString toLowerCase()

publicString toUpperCase()

publicString toUpperCase(Locale)

publicString trim()

publicfinal void wait() throws InterruptedException

publicfinal void wait(long,int) throws InterruptedException

publicfinal native void wait(long) throws InterruptedException

publicfinal native Class getClass()

publicfinal native void notify()

publicfinal native void notifyAll()

publicdefault IntStream chars()

publicdefault IntStream codePoints()

publicString(byte[],int,int)

publicString(byte[],Charset)

publicString(byte[],String) throws UnsupportedEncodingException

publicString(byte[],int,int,Charset)

publicString(byte[],int,int,String) throws UnsupportedEncodingException

publicString(StringBuilder)

publicString(StringBuffer)

publicString(byte[])

publicString(int[],int,int)

publicString()

publicString(char[])

publicString(String)

publicString(char[],int,int)

publicString(byte[],int)

publicString(byte[],int,int,int)

若能去掉象java.lang 这样的限定词,结果显然会更令人满意。

1.3     代码2

import java.lang.reflect.*;

import java.io.*;

public class ShowMethodsClean {

    static finalString usage= "usage: \n" + "ShowMethodsCleanqualified.class.name\n"

            + "To show all methods in class or: \n"+ "ShowMethodsClean qualif.class.nameword\n"

            + "To search for methods involving 'word'";

 

    public staticvoidmain(String[] args){

        if (args.length< 1) {

            System.out.println(usage);

            System.exit(0);

        }

        try {

            Class c = Class.forName(args[0]);

            Method[] m = c.getMethods();

            Constructor[] ctor = c.getConstructors();

            // Convert to an array of cleaned Strings:

            String[] n = new String[m.length + ctor.length];

            for (int i = 0; i < m.length; i++) {

                String s = m[i].toString();

                n[i] = StripQualifiers.strip(s);

            }

            for (int i = 0; i < ctor.length; i++) {

                String s = ctor[i].toString();

                n[i + m.length] = StripQualifiers.strip(s);

            }

            if (args.length== 1)

                for (int i = 0; i < n.length; i++)

                    System.out.println(n[i]);

            else

                for (int i = 0; i < n.length; i++)

                    if (n[i].indexOf(args[1]) != -1)

                        System.out.println(n[i]);

        } catch (ClassNotFoundException e) {

            System.out.println("No such class: "+ e);

        }

    }

}

 

class StripQualifiers {

    private StreamTokenizer st;

 

    public StripQualifiers(String qualified) {

        st = new StreamTokenizer(new StringReader(qualified));

        st.ordinaryChar(' '); // Keep thespaces

    }

 

    public String getNext() {

        String s = null;

        try {

            if (st.nextToken() != StreamTokenizer.TT_EOF) {

                switch (st.ttype) {

                case StreamTokenizer.TT_EOL:

                    s = null;

                    break;

                case StreamTokenizer.TT_NUMBER:

                    s = Double.toString(st.nval);

                    break;

                case StreamTokenizer.TT_WORD:

                    s = new String(st.sval);

                    break;

                default: // single character in ttype

                    s = String.valueOf((char) st.ttype);

                }

            }

        } catch (IOException e) {

            System.out.println(e);

        }

        return s;

    }

 

    public staticString strip(String qualified){

        StripQualifiers sq = new StripQualifiers(qualified);

        String s = "", si;

        while ((si = sq.getNext()) != null) {

            int lastDot = si.lastIndexOf('.');

            if (lastDot != -1)

                si = si.substring(lastDot+ 1);

            s += si;

        }

        return s;

    }

} /// :~

1.4     执行如下

usage:

ShowMethodsCleanqualified.class.name

Toshow all methods in class or:

ShowMethodsCleanqualif.class.name word

Tosearch for methods involving 'word'

ShowMethodsClean 方法非常接近前一个ShowMethods,只是它取得了Method和Constructor 数组,并将它们转换成单个String数组。随后,每个这样的String 对象都在StripQualifiers.Strip()里“过”一遍,删除所有方法限定词。正如看到的那样,此时用到了StreamTokenizer 和String 来完成这个工作。

假如记不得一个类是否有一个特定的方法,而且不想在联机文档里逐步检查类结构,或者不知道那个类是否能对某个对象(如Color 对象)做某件事情,该工具便可节省大量编程时间。

2     总结

利用RTTI 可根据一个匿名的基础类句柄调查出类型信息。但正是由于这个原因,新手们极易误用它,因为有些时候多形性方法便足够了。对那些以前习惯程序化编程的人来说,极易将他们的程序组织成一系列switch语句。他们可能用RTTI 做到这一点,从而在代码开发和维护中损失多形性技术的重要价值。Java 的要求是让我们尽可能地采用多形性,只有在极特别的情况下才使用RTTI。

但为了利用多形性,要求我们拥有对基础类定义的控制权,因为有些时候在程序范围之内,可能发现基础类并未包括我们想要的方法。若基础类来自一个库,或者由别的什么东西控制着,RTTI 便是一种很好的解决方案:可继承一个新类型,然后添加自己的额外方法。在代码的其他地方,可以侦测自己的特定类型,并调用那个特殊的方法。这样做不会破坏多形性以及程序的扩展能力,因为新类型的添加不要求查找程序中的switch 语句。但在需要新特性的主体中添加新代码时,就必须用RTTI 侦测自己特定的类型。

从某个特定类的利益的角度出发,在基础类里加入一个特性后,可能意味着从那个基础类衍生的其他所有类都必须获得一些无意义的“鸡肋”。这使得接口变得含义模糊。若有人从那个基础类继承,且必须覆盖抽象方法,这一现象便会使他们陷入困扰。比如现在用一个类结构来表示乐器(Instrument)。假定我们想清洁管弦乐队中所有适当乐器的通气音栓(SpitValve),此时的一个办法是在基础类Instrument 中置入一个ClearSpitValve()方法。但这样做会造成一个误区,因为它暗示着打击乐器和电子乐器中也有音栓。针对这种情况,RTTI 提供了一个更合理的解决方案,可将方法置入特定的类中(此时是Wind,即“通气口”)——这样做是可行的。但事实上一种更合理的方案是将prepareInstrument()置入基础类中。初学者刚开始时往往看不到这一点,一般会认定自己必须使用RTTI。

最后,RTTI 有时能解决效率问题。若代码大量运用了多形性,但其中的一个对象在执行效率上很有问题,便可用RTTI 找出那个类型,然后写一段适当的代码,改进其效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值