你如何findJava中给定类的所有子类?
一个人如何去尝试在Java中查找给定类(或给定接口的所有实现者)的所有子类? 到目前为止,我有一个方法来做到这一点,但我觉得效率很低(至less可以这么说)。 方法是:
- 获取类path上存在的所有类名称的列表
- 加载每个类并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
来获取抽象类的子类:
将它们添加到(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
来声明类ExampleImpl
是ExampleImpl
的一个实现。
然后,您可以获得每个实例的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.