如何在Java中查找给定类的所有子类(或给定接口的所有实现者)?
目前,我有一种方法来完成这个任务,但我发现它相当低效(至少可以这么说)。
该方法是:
- 获取类路径上存在的所有类名的列表
- 加载每个类并测试以查看它是否是所需类或接口的子类或实现者
在Eclipse中,有一个很好的功能称为Type Hierarchy可以高效地显示此信息。
如何以编程方式完成这项工作?
如何在Java中查找给定类的所有子类(或给定接口的所有实现者)?
目前,我有一种方法来完成这个任务,但我发现它相当低效(至少可以这么说)。
该方法是:
在Eclipse中,有一个很好的功能称为Type Hierarchy可以高效地显示此信息。
如何以编程方式完成这项工作?
使用纯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
}
这种方法的额外好处是使用字节码分析器来查找候选者,这意味着它不会加载它扫描到的所有类。
除了您描述的方法,没有其他方法可以实现它。想一想 - 没有扫描类路径上的每个类,如何知道哪些类扩展ClassX?
Eclipse只能在按下“在类型层次结构中显示”按钮时告诉您关于超类和子类的信息,因为它已经在该点加载了所有类型数据(因为它不断编译您的类,知道类路径上的所有内容等),从而表现出似乎是“高效”的时间。
reflections.getSubTypesOf(aClazz))
即可。 链接 - Enwired仅使用内置的Java Reflections API 是不可能实现这个功能的。
存在一个项目可以进行必要的扫描和索引类路径,以便您可以访问此信息...
一种基于运行时元数据分析的Java库,类似于Scannotations
Reflections扫描您的类路径,索引元数据,并允许您在运行时查询它,并可能为项目中的许多模块保存和收集该信息。使用Reflections,您可以查询以下元数据:
- 获取某个类型的所有子类型
- 获取带有某些注释的所有类型
- 获取带有某些注释的所有类型,包括匹配注释参数
- 获取某些方法的所有注释
(声明:我没有使用过它,但该项目的描述似乎完全符合您的需求。)
尝试使用ClassGraph(免责声明,我是作者)。ClassGraph支持在运行时或构建时扫描给定类的子类,但还支持更多功能。ClassGraph可以在内存中构建整个类图的抽象表示(所有类、注释、方法、方法参数和字段),适用于类路径上的所有类或选定包中的类,并且您可以随意查询此类图。ClassGraph支持比其他任何扫描器更多的类路径规范机制和类加载器,并且与新的JPMS模块系统无缝配合,因此如果您的代码基于ClassGraph,则您的代码将具有最大的可移植性。在此处查看API。
我知道我晚了几年才参加这个聚会,但我碰到了同样的问题并试图解决它。如果您正在编写Eclipse插件(因此可以利用其缓存等功能),则可以在程序上使用Eclipse的内部搜索来查找实现接口的类。以下是我的(非常粗糙的)第一次尝试:
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();
}
}
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;
}
}
}
为了简化,我删除了命令行参数解析,并且将内容写入 System.out 而不是文件。
请注意其他答案中提到的限制,您还可以使用 openpojo 的 PojoClassFactory
(可在 Maven 上获得),方法如下:
for(PojoClass pojoClass : PojoClassFactory.enumerateClassesByExtendingType(packageRoot, Superclass.class, null)) {
System.out.println(pojoClass.getClazz());
}
其中packageRoot
是您希望搜索的包的根字符串(例如"com.mycompany"
或仅"com"
),而Superclass
是您的超类型(这也适用于接口)。
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.";
}
}
META-INF/services/com.example.Example
并将文本 com.example.ExampleImpl
放置其中来声明类 ExampleImpl
是 Example
的实现。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.
值得注意的是,这只能找到当前类路径上存在的所有子类。假设目前您正在查看的内容可以接受,而且很可能您已经考虑过这一点,但是如果您曾经发布了一个非final
类到公共场合(不同程度的“公共场合”),那么其他人编写的您不知道的子类也完全有可能存在。
因此,如果您想要查看所有子类,因为您想要进行更改并查看它如何影响子类的行为,请记住您无法看到的子类。理想情况下,所有非私有方法和类本身都应该有良好的文档记录;根据该文档进行更改,而不改变方法/非私有字段的语义,您的更改应该是向后兼容的,至少对于遵循您定义的超类的子类来说是这样的。