Java运行时的子类识别

Java运行时的子类识别
(仙人掌工作室 2001年09月04日 11:19)

Java映像API(Reflection API)和Java 接口为编写可重用的代码提供了优秀的工具。以一个通用的命令启动器为例:假设你有一组执行各种任务的类,比如关闭或打开电灯,打开、关闭或锁上门,等等。这些类的名字分别是LightOn、LightOff、DoorOpen、DoorClose和DoorLock,所有这些类都实现了Command接口。

Command接口的定义如下:

public interface Command {
  public void process();
}

你可以编写一个简单的通用启动器,如下所示:

public class Launcher{
  public static void main(String[] args){
        if (args.length>0) {
           try {
              Command command =
                 (Command)Class.forName(args[0]).newInstance();
              command.process();
           } catch (Exception ex) {
              System.out.println("Invalid command");
           }
        } else {
           System.out.println("Usage: Launcher <command>");
        }
     }
// Launcher

这个程序用Class.forName方法获得参数中指定类的Class对象,然后用newInstance()方法创建该类的一个实例。根据要求,该类实现了Command接口,所以程序把对象定型(cast)成为Command,然后调用process()方法,由process方法执行实际任务。如果出现了异常,比如由于类的名字拼写错误或安全方面的问题,程序将显示一个“Invalid command”信息。

这个命令启动器可以按照如下方式使用:

%java Launcher LightOn

以后如果实现了一些新的任务,命令启动器也不需要修改。从程序员的角度来看,这确实很不错。但是,它对于用户来说又如何呢?假设一个用户输入了以下命令:

%java Launcher OpenDoor
Invalid command

“Invalid command”的意思是用户不能打开门吗?不是,它只表示类命名错误(DoorOpen变成了OpenDoor)。所以,程序应该允许用户查看可用命令的清单。要保证命令启动器的通用性,用户应该能够在运行时查找这些命令。

Java映像API能够在运行时提供大量有关指定类的信息:我们可以方便地获知指定类的所有超类、它所实现的接口、方法、构造函数、域,等等。但在这里,我们感兴趣的是所有实现特定接口的类,这种信息无法从Java映像API直接获得。本文余下的部分就为你介绍如何获取实现了特定接口的类的信息。

在Java中,包对应着目录,通过File对象的list()方法获取包含在包中的所有类是很容易的。我们的做法是利用instanceof语句进行检查:对于包里面的每一个类文件,相应的类是否实现了Command接口。这意味着只检查每一个类文件的公用类,而且接口和它的实现必须在一个包里面。下面是代码:
public static void find(String pckgname) {
// 把包名字转换成绝对路径
String name = new String(pckgname);
if (!name.startsWith("/")) {
name = "/" + name;
}

name = name.replace('.','/');

// 获得一个File对象
URL url = Launcher.class.getResource(name);
File directory = new File(url.getFile());

if (directory.exists()) {
// 获得包里面的文件清单
String [] files = directory.list();
for (int i=0;I&lt;files.length;i++) {

// 我们只对.class文件感兴趣
if (files[i].endsWith(".class")) {
// 删除.class文件扩展名
String classname = files[i].substring(0,files[i].length()-6);
try {
// 尝试创建该对象的一个实例
Object o = Class.forName(pckgname+"."+classname).newInstance();
if (o instanceof Command) {
System.out.println(classname);
}
} catch (ClassNotFoundException cnfex) {
System.err.println(cnfex);
} catch (InstantiationException iex) {
// 我们试图实例化一个接口或者
// 一个没有默认构造函数的对象
} catch (IllegalAccessException iaex) {
// 该类不是公用类
}
}
}
}
}

要执行手头的任务,我们只需稍微修改一下原来的启动器。现在,我们可以设想接口和它的实现在一个commands包里面:

public static void main(String[] args){
if (args.length&gt;0) {
try {
Command command = (Command)Class.forName("commands."+
args[0]).newInstance();
command.process();
} catch (Exception ex) {
System.out.println("Invalid command");
System.out.println("Available commands:");
find("commands");

}
} else {
System.out.println("Usage: Launcher &lt;command&gt;");
}
}


下面是执行错误的命令时,改进后的启动器显示的结果:

%java Launcher OpenDoor
Invalid command
Available commands:
LightOn
LightOff
DoorOpen
DoorClose
DoorLock

我们可以修改find()方法,让它能够寻找指定类的任何子类。为此,我们要用到instanceof的动态版本,即isInstance()。用(tosubclass.isInstance(o))替换(o instanceof Command),其中tosubclass是find()方法参数中指定的类。现在我们有了一个方法,它能够在指定的包中找出指定类的任何子类。我们可以改进这个方法,让它在当前已装入的包中寻找子类。为此,我们要用到Package.getPackages()方法,这个方法精确地返回当前类装载器装入的各个包。然后,我们只需针对每一个包调用find()方法:
public static void find(String tosubclassname) {
try {
Class tosubclass = Class.forName(tosubclassname);
Package [] pcks = Package.getPackages();
for (int i=0;I&lt;pcks.length;i++) {
find(pcks[i].getName(),tosubclass);
}
} catch (ClassNotFoundException ex) {
System.err.println("Class "+tosubclassname+" not found!");
}
}
这个方法的返回结果主要依赖于它被调用的时间。对于本文的通用命令启动器,调用find()方法时装入内存的只有少量几个包。例如,下面是在我的NT机器上调用find()之前装入的包:

package java.util.zip,Java Platform API Specification,version 1.3
package java.security,Java Platform API Specification,version 1.3
package java.io,Java Platform API Specification,version 1.3
package sun.net.www.protocol.file,Java Platform API Specification,version 1.3
package sun.net.www.protocol.jar,Java Platform API Specification,version 1.3
package sun.net.www,Java Platform API Specification,version 1.3
package java.util.jar,Java Platform API Specification,version 1.3
package sun.security.action,Java Platform API Specification,version 1.3
package java.lang,Java Platform API Specification,version 1.3
package sun.io,Java Platform API Specification,version 1.3
package java.util,Java Platform API Specification,version 1.3
package sun.misc,Java Platform API Specification,version 1.3
package java.security.cert,Java Platform API Specification,version 1.3
package java.lang.reflect,Java Platform API Specification,version 1.3
package java.net,Java Platform API Specification,version 1.3
package sun.security.util,Java Platform API Specification,version 1.3
package java.lang.ref,Java Platform API Specification,version 1.3
package sun.security.provider,Java Platform API Specification,version 1.3
package com.sun.rsajca

因为在命令启动器中,接口和它的所有实现都在同一个包里面,装入类之后就可以得到已经装入的包。因此,我们可以在该包里面搜索子类。这是寻找相关包的唯一方法。RTSI类的完整源代码可以在本文最后的参考资源找到。解开下载包的ZIP压缩之后,你可以用下面的命令测试代码:

% java -cp classes RTSI commands.Command

当包以操作系统目录和文件的形式存在时,前面讨论的代码能够顺利地运行;但如果类文件在一个或者多个jar文件里面,这些代码不再有效。在本文的下载代码中,你将发现该问题的一个解决方案。你可以用下面的命令测试程序处理jar文件的能力:
% java -jar RTSI.jar commands.Command
■ 结束语
在这篇文章中,我们讨论了如何在一个指定的包里面(或从已经装入的包)动态地提取所有指定类的子类。这个功能不仅对于设计通用程序很有用,而且正如本文的命令启动器实例所显示的,它对用户同样有好处。

作者申明:一些读者指出,本文的程序只能检测拥有默认构造函数的子类。他们建议用Class的isAssignableFrom()方法替代isInstance()。

■ 参考资源:
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值