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()。

■ 参考资源:

本文的完整源代码 Java的Class文档 Java的Package文档 (责任编辑 吴北 jiaoxq@staff.ccidnet.com)

转载于:https://www.cnblogs.com/sunsonbaby/archive/2004/12/20/79318.html

Java运行时的子类识别相关推荐

  1. java运行时跟编译时的区别,欢迎大家指正

    个人博客地址:https://blog.csdn.net/qq_41907991 关于java运行时及编译时期的区别: 首先我们要了解编译以及运行的概念: 编译就是指,编译器帮你把源码翻译成机器能识别 ...

  2. JAVA运行时异常及检查式异常

    欢迎支持笔者新作:<深入理解Kafka:核心设计与实践原理>和<RabbitMQ实战指南>,同时欢迎关注笔者的微信公众号:朱小厮的博客. 欢迎跳转到本文的原文链接:https: ...

  3. 【java】Java运行时动态生成类几种方式

    1.概述 转载:Java运行时动态生成类几种方式 这里发现自己不知道的,原来Java 还能自己编译自己,学到了. 最近一个项目中利用规则引擎,提供用户拖拽式的灵活定义规则.这就要求根据数据库数据动态生 ...

  4. Java运行时数据区及对象的分配

    一.Java运行时数据区 简图: 简述: 堆.方法区是线程共享的,虚拟机栈.程序计数器.本地方法栈是线程私有的,一个线程一份. 虚拟机栈的基本单位是栈帧,一个方法的开始执行意味着一个栈帧进栈,一个方法 ...

  5. 什么是引发?Java运行时系统引发的异常如何处理?

    到目前为止,你只是获取了被Java运行时系统引发的异常.然而,程序可以用throw语句引发明确的异常.Throw语句的通常形式如下: throw ThrowableInstance; 这里,Throw ...

  6. oracle java rmi 漏洞,Oracle Java SE Java运行时环境RMI子组件远程漏洞(CVE-2011-3556)

    发布日期:2011-10-20 更新日期:2011-10-20 受影响系统: Oracle Sun JRE 1.6.x Oracle Sun JDK 1.6.x 不受影响系统: Oracle Sun ...

  7. Adobe flash cs5 的Java运行时环境初始化错误 完美解决方法

    Adobe flash cs5 的Java运行时环境初始化错误 完美解决方法 下载网络上的Adobe flash cs5 精简版(绿色版),Java运行时环境初始化时出现错误,你可能需要重装Flash ...

  8. 2019年Java和JVM生态系统预测:OpenJDK将成为Java运行时市场领导者

    本文对2019年Java和JVM生态系统做了一些预测. 正如InfoQ 2018年度总结中说的那样,Java在2018年的发展势头非常有意思. 在我们步入2019之际,让我们来看看在新的一年中Java ...

  9. Java怎么xml拒绝,Sun Java运行时环境XML解析拒绝服务漏洞

    影响版本: Sun JDK 6 Sun JDK 5.0 Sun JRE 6 Sun JRE 5.0 漏洞描述: BUGTRAQ  ID: 35958 CVE(CAN) ID: CVE-2009-262 ...

最新文章

  1. 数据中台-阿里巴巴的数据整合、价值发掘、社会赋能之道
  2. python学费多少-python培训学费是多少?
  3. C/C++ 中变量的声明、定义、初始化的区别
  4. Derby数据库备份
  5. Delphi开发中增删改查操作以及存储过程的调用方式
  6. 第三,四章 数据库和数据表的(增删改查,重命名等)基本操作
  7. javaweb实训第四天下午——员工管理系统-JSPServletJDBC综合练习-CRUD
  8. 金士顿DT100G3(16G) U盘修复
  9. Oracle imp 导入dmp文件到数据库
  10. 【HAVENT原创】使用 Spring Boot 的 AOP 自定义注解
  11. meta标签是什么,通常包含哪些内容?
  12. matlab方程求解的实验,实验七用matlab求解常微分方程
  13. 电商后台系统:管理后台之账号管理(一)
  14. 《黑匣子思维:我们如何更理性地犯错》iphone部分
  15. OpenVINO™基于人脸landmark检测实现眼睛疲劳/睡意检测
  16. 性能测试测试方案总结
  17. stm32f767之ADC
  18. Redis新版本发布,你还认为Redis是单线程?
  19. 【计算机网络】分层结构(OSI 7层模型、TCP/TP 4层模型、5层参考模型)
  20. 学计算机的前后对比,2020计算机考研(408)大纲前后对比分析!

热门文章

  1. 15-自己写字符串库函数
  2. python django 动态网页_python27+django1.9创建app的视图及实现动态页面
  3. 车机没有carlife可以自己下载吗_路咖评:新宝骏的车机系统 革了百度Carlife的命?...
  4. mybatis 级联查询兑现_MyBatis之自查询使用递归实现 N级联动效果(两种实现方式)...
  5. 数据结构 创建顺序表
  6. YFI 创始人 Andre Cronje:有四个新产品待发布,但担心在推出后被分叉
  7. BitMEX将于2月16日16:00引入防止输入错误交易规则
  8. 数据:DOGE近24小时交易额约为107.91亿美元
  9. 去中心化保险协议InsurAce完成100万美元种子轮融资,DeFiance Capital领投
  10. 重磅分享--基于违约概率跟odds的经验评分