你如何findJava中给定类的所有子类?

一个人如何去尝试在Java中查找给定类(或给定接口的所有实现者)的所有子类? 到目前为止,我有一个方法来做到这一点,但我觉得效率很低(至less可以这么说)。 方法是:

  1. 获取类path上存在的所有类名称的列表
  2. 加载每个类并testing它是否是所需类或接口的子类或实现者

在Eclipse中,有一个叫做Type Hierarchy的很好的特性,可以很有效地显示出来。 人们如何去做,并以编程方式进行?

除了你所描述的,没有别的办法可以做。 想想看 – 如何知道什么类扩展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<BeanDefinition> 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 = <get your project here>; 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<SearchMatch> results = new LinkedList<SearchMatch>(); 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运行它:

 <javadoc sourcepath="${src}" packagenames="*" > <doclet name="com.example.ObjectListDoclet" path="${build}"/> </javadoc> 

这是基本的代码:

 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来获取抽象类的子类:

https://github.com/xmeng1/ReflectionsDemo

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

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

这是如何完成的:

 Reflections reflections = new Reflections("my.project"); Set<Class<? extends SomeType>> 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来声明类ExampleImplExampleImpl的一个实现。

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

 ServiceLoader<Example> 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.