java中每个类都有子类,你如何findJava中给定类的所有子类?

除了你所描述的,没有别的办法可以做。 想想看 – 如何知道什么类扩展ClassX而不扫描类path中的每个类?

Eclipse只能告诉你有关超级和子类的东西,因为在按下“Display in Type Hierarchy”button的时候已经加载了所有的types数据(因为它是不断编译你的类,知道类path上的所有内容等)。

用纯Java来扫描类是不容易的。

Spring框架提供了一个名为ClassPathScanningCandidateComponentProvider的类,可以完成你所需要的任务。 以下示例将在包org.example.package中查找MyClass的所有子类

ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); provider.addIncludeFilter(new AssignableTypeFilter(MyClass.class)); // scan in org.example.package Set components = provider.findCandidateComponents("org/example/package"); for (BeanDefinition component : components) { Class cls = Class.forName(component.getBeanClassName()); // use class cls found }

这种方法还有一个好处,就是使用字节码分析器来查找候选,这意味着它不会加载它扫描的所有类。

这是不可能的,只使用内置的JavareflectionAPI。

有一个项目存在,它会对您的类path进行必要的扫描和索引,以便您可以访问这些信息。

思考

一个Java运行时元数据分析,本着Scannotations的精神

reflection扫描您的类path,索引元数据,允许您在运行时查询它,并可以保存和收集项目中许多模块的信息。

使用reflection,您可以查询您的元数据:

获取某种types的所有子types

得到所有types的注释与一些注释

获取所有types的注释,包括注释参数匹配

得到所有的方法用一些注释

(免责声明:我没有使用它,但项目的描述似乎完全适合您的需求。)

不要忘记,为类生成的Javadoc将包含已知子类(以及接口,已知实现类)的列表。

我知道我晚了几年,但是我遇到了这个问题,试图解决同样的问题。 你可以使用Eclipse的内部search程序,如果你正在编写一个Eclipse插件(从而利用它们的caching等)来寻找实现一个接口的类。 这是我的(非常粗糙)第一次切割:

protected void listImplementingClasses( String iface ) throws CoreException { final IJavaProject project = ; try { final IType ifaceType = project.findType( iface ); final SearchPattern ifacePattern = SearchPattern.createPattern( ifaceType, IJavaSearchConstants.IMPLEMENTORS ); final IJavaSearchScope scope = SearchEngine.createWorkspaceScope(); final SearchEngine searchEngine = new SearchEngine(); final LinkedList results = new LinkedList(); searchEngine.search( ifacePattern, new SearchParticipant[]{ SearchEngine.getDefaultSearchParticipant() }, scope, new SearchRequestor() { @Override public void acceptSearchMatch( SearchMatch match ) throws CoreException { results.add( match ); } }, new IProgressMonitor() { @Override public void beginTask( String name, int totalWork ) { } @Override public void done() { System.out.println( results ); } @Override public void internalWorked( double work ) { } @Override public boolean isCanceled() { return false; } @Override public void setCanceled( boolean value ) { } @Override public void setTaskName( String name ) { } @Override public void subTask( String name ) { } @Override public void worked( int work ) { } }); } catch( JavaModelException e ) { e.printStackTrace(); } }

到目前为止,我所看到的第一个问题是,我只捕获直接实现接口的类,而不是所有的子类 – 但是一点recursion从来不会伤害任何人。

我几年前就这样做了。 执行此操作的最可靠的方法(即使用官方的Java API而不需要外部依赖)是编写一个自定义的doclet来生成一个可以在运行时读取的列表。

你可以像这样从命令行运行它:

javadoc -d build -doclet com.example.ObjectListDoclet -sourcepath java/src -subpackages com.example

或者像这样从ant运行它:

这是基本的代码:

public final class ObjectListDoclet { public static final String TOP_CLASS_NAME = "com.example.MyClass"; /** Doclet entry point. */ public static boolean start(RootDoc root) throws Exception { try { ClassDoc topClassDoc = root.classNamed(TOP_CLASS_NAME); for (ClassDoc classDoc : root.classes()) { if (classDoc.subclassOf(topClassDoc)) { System.out.println(classDoc); } } return true; } catch (Exception ex) { ex.printStackTrace(); return false; } } }

为了简单起见,我已经删除了命令行参数parsing,并且正在写入System.out而不是文件。

记住其他答案中提到的限制,您也可以按照以下方式使用openpojo的PojoClassFactory ( Maven提供 ):

for(PojoClass pojoClass : PojoClassFactory.enumerateClassesByExtendingType(packageRoot, Superclass.class, null)) { System.out.println(pojoClass.getClazz()); }

其中packageRoot是你想要search的包的根string(例如"com.mycompany"或者甚至是"com" ),而Superclass是你的超types(这也适用于接口)。

应该注意的是,这当然只能find当前类path中存在的所有子类。 大概这对你目前正在看的是好的,而且你有机会考虑这个,但是如果你在任何时候发布了一个非final课程(对于不同级别的“野生”),那么这是完全可行的别人已经编写了你自己不知道的子类。

因此,如果您碰巧希望看到所有的子类,因为您想要进行更改,并且要查看它是如何影响子类的行为的,请记住您看不到的子类。 理想情况下,你所有的非私人方法,以及class级本身都应该有详细的logging; 根据本文档进行更改,而不更改方法/非专用字段的语义,并且您的更改应该是向后兼容的,对于至less跟随您的超类定义的任何子类。

你看到你的实现和Eclipse之间的区别的原因是你每次扫描,而Eclipse(和其他工具)只扫描一次(在大多数时间在项目加载期间),并创build一个索引。 下一次你要求的数据,它不会再扫描,但看看索引。

您可以尝试我的库FastClasspathScanner – 它可以在类path中find给定类的所有子类(以及给定接口的所有子接口,实现给定接口的所有类,使用给定注释注释的所有类等等) 。 这是一个小的依赖关系,与其他类path扫描选项相比,速度非常快。

顺便说一句,扫描类path并不像检查java.class.path属性那么简单,因为有许多方式可以指定类path(例如,可以将Class-Path条目添加到jar文件的清单)。 FastClasspathScanner为您处理这些复杂性。

我只写了一个简单的演示来使用org.reflections.Reflections来获取抽象类的子类:

将它们添加到(this.getClass()。getName())父类构造函数(或创build一个默认的)的静态映射中,但是这将在运行时更新。 如果懒惰初始化是一个选项,你可以尝试这种方法。

我正在使用reflection库,它会扫描您的类path的所有子类: https : //github.com/ronmamo/reflections

这是如何完成的:

Reflections reflections = new Reflections("my.project"); Set> subTypes = reflections.getSubTypesOf(SomeType.class);

根据您的特定要求,在某些情况下,Java的服务加载器机制可能会实现您的目标。

简而言之,它允许开发人员通过在JAR / WAR文件的META-INF/services目录中的文件中列出一个类来明确声明某个类是其他类的子类(或者实现了某个接口)。 然后可以使用java.util.ServiceLoader类来发现它,当给定一个Class对象时,它将生成该类的所有声明的子类的实例(或者,如果Class表示一个接口,则实现该接口的所有类)。

这种方法的主要优点是不需要手动扫描子类的整个类path – 所有的发现逻辑都包含在ServiceLoader类中,它只加载在META-INF/services目录中明确声明的类(不是classpath中的每个类)。

但是,有一些缺点:

它不会find所有的子类,只有那些明确声明的子类。 因此,如果你需要真正find所有的子类,这种方法可能是不够的。

它要求开发人员在META-INF/services目录下显式声明这个类。 这对开发者来说是一个额外的负担,并且容易出错。

ServiceLoader.iterator()生成子类实例,而不是它们的Class对象。 这导致两个问题:

对于如何构造子类,您没有任何说法 – 使用no-arg构造函数来创build实例。

因此,子类必须有一个默认的构造函数,或者必须声明一个无参数的构造函数。

显然,Java 9将解决这些缺点中的一些(特别是关于子类的实例化的那些缺点)。

一个例子

假设你有兴趣find实现了接口com.example.Example类。 com.example.Example :

package com.example; public interface Example { public String getStr(); }

类com.example.ExampleImpl实现该接口:

package com.example; public class ExampleImpl implements Example { public String getStr() { return "ExampleImpl's string."; } }

您将通过创build包含文本com.example.ExampleImpl的文件META-INF/services/com.example.Example来声明类ExampleImpl是ExampleImpl的一个实现。

然后,您可以获得每个实例的Example (包括ExampleImpl一个实例),如下所示:

ServiceLoader loader = ServiceLoader.load(Example.class) for (Example example : loader) { System.out.println(example.getStr()); } // Prints "ExampleImpl's string.", plus whatever is returned // by other declared implementations of com.example.Example.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值