如何在Java中查找给定类的所有子类(或给定接口的所有实现者)?到目前为止,我有一种方法可以执行此操作,但是我发现它效率很低(至少可以这样说)。方法是:
- 获取类路径上存在的所有类名称的列表
- 加载每个类并测试以查看它是否是所需类或接口的子类或实现者
在Eclipse中,有一个很好的功能,称为类型层次结构(Type Hierarchy),可以很有效地显示这一点。如何进行编程?
如何在Java中查找给定类的所有子类(或给定接口的所有实现者)?到目前为止,我有一种方法可以执行此操作,但是我发现它效率很低(至少可以这样说)。方法是:
在Eclipse中,有一个很好的功能,称为类型层次结构(Type Hierarchy),可以很有效地显示这一点。如何进行编程?
Answers:
除了您所描述的以外,没有其他方法可以做到。想想看-谁能在不扫描类路径上每个类的情况下知道哪些类扩展了ClassX?
Eclipse只能在看来“有效”的时间内告诉您有关父类和子类的信息,因为在按“显示类型层次结构”按钮时,它已经加载了所有类型数据(因为不断地编译您的类,了解类路径上的所有内容,等等)。
使用纯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
}
此方法的另一个好处是使用字节码分析器查找候选对象,这意味着它不会加载它扫描的所有类。
仅使用内置的Java Reflections API不可能做到这一点。
存在一个对类路径进行必要的扫描和索引的项目,以便您可以访问此信息...
遵循Scannotations精神的Java运行时元数据分析
Reflections扫描您的类路径,为元数据建立索引,使您可以在运行时对其进行查询,并可以保存和收集项目中许多模块的信息。使用反射,您可以查询元数据以:
- 获取某种类型的所有子类型
- 使所有类型都带有一些注释
- 获取所有带有注释的类型,包括与注释参数匹配的类型
- 得到所有带有一些注释的方法
(免责声明:我没有使用过,但是该项目的描述似乎完全符合您的需求。)
尝试ClassGraph。(免责声明,我是作者)。ClassGraph支持在运行时或构建时扫描给定类的子类,但还可以进行更多扫描。ClassGraph可以在内存中,类路径上的所有类或白名单包中的类中构建整个类图(所有类,批注,方法,方法参数和字段)的抽象表示,但是您可以查询该类图你要。与其他任何扫描仪相比,ClassGraph支持更多的类路径规范机制和类加载器,并且还可以与新的JPMS模块系统无缝协作,因此,如果您的代码基于ClassGraph,则代码将具有最大的可移植性。请参阅此处的API。
我几年前做了。最可靠的方法(即使用正式的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;
}
}
}
为简单起见,我删除了命令行参数解析,而是写入System.out而不是文件。
我知道我参加这个聚会已经晚了几年,但是我遇到了这个问题,试图解决同样的问题。如果要编写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();
}
}
到目前为止,我看到的第一个问题是,我只捕获了直接实现接口的类,而不是所有子类-但是一点递归永远不会伤害任何人。
记住其他答案中提到的限制,您还可以按以下方式使用openpojoPojoClassFactory
(在Maven上可用):
for(PojoClass pojoClass : PojoClassFactory.enumerateClassesByExtendingType(packageRoot, Superclass.class, null)) {
System.out.println(pojoClass.getClazz());
}
packageRoot
您想搜索的包的根字符串在哪里(例如"com.mycompany"
,甚至只是"com"
),并且Superclass
是您的超类型(这也适用于接口)。
我只是编写了一个简单的演示程序,以使用org.reflections.Reflections
来获取抽象类的子类:
根据您的特定要求,在某些情况下,Java的服务加载程序机制可能会实现您所追求的目标。
简而言之,它允许开发人员通过在JAR / WAR文件META-INF/services
目录中的文件中列出某个类来显式声明该类是某个其他类的子类(或实现某些接口)。然后可以使用java.util.ServiceLoader
该类发现该类,当给定Class
对象时,该类将生成该类的所有声明的子类的实例(或者,如果Class
表示一个接口,则是实现该接口的所有类)。
这种方法的主要优点是,无需手动扫描整个类路径中的子类-所有发现逻辑都包含在ServiceLoader
该类中,并且仅加载META-INF/services
目录中显式声明的类(而不是类路径中的每个类) 。
但是,有一些缺点:
META-INF/services
目录下显式声明该类。这是开发人员的额外负担,并且容易出错。ServiceLoader.iterator()
生成子类的实例,而不是他们Class
的对象。这导致两个问题:
显然,Java 9将解决其中的一些缺点(尤其是有关子类实例化的缺点)。
假设您对查找实现接口的类感兴趣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.";
}
}
您将通过创建一个包含text 的文件来声明该类ExampleImpl
是的实现。Example
META-INF/services/com.example.Example
com.example.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.
还应该注意的是,这当然只会找到当前类路径中存在的所有那些子类。大概这对于您当前正在查看的内容是可以的,并且您确实有考虑过这一点,但是如果您在任何时候都将一个非final
类放到野外(对于不同级别的“野”),则完全可行别人已经编写了您自己不知道的子类。
因此,如果您由于要进行更改而恰好想要查看所有子类,并想知道它如何影响子类的行为-请记住您看不到的子类。理想情况下,所有非私有方法以及类本身都应有充分的文档记录;在不更改方法/非私有字段的语义的情况下,根据本文档进行更改,并且对于至少遵循超类定义的任何子类,您的更改应向后兼容。
之所以看到实现与Eclipse之间存在差异,是因为您每次都进行扫描,而Eclipse(和其他工具)仅扫描一次(在大多数项目加载过程中)并创建索引。下次您请求数据时,它不会再次扫描,而是查看索引。
我正在使用反射库,它会扫描您的类路径中的所有子类:https : //github.com/ronmamo/reflections
这是这样做的:
Reflections reflections = new Reflections("my.project");
Set<Class<? extends SomeType>> subTypes = reflections.getSubTypesOf(SomeType.class);
将它们添加到父类构造函数内部的(this.getClass()。getName())静态映射中(或创建一个默认映射),但这将在运行时更新。如果可以选择延迟初始化,则可以尝试这种方法。
您可以使用org.reflections库,然后创建Reflections类的对象。使用此对象,可以获得给定类的所有子类的列表。 https://www.javadoc.io/doc/org.reflections/reflections/0.9.10/org/reflections/Reflections.html
Reflections reflections = new Reflections("my.project.prefix");
System.out.println(reflections.getSubTypesOf(A.class)));
我需要将其作为测试用例,以查看是否将新类添加到了代码中。这就是我所做的
final static File rootFolder = new File(SuperClass.class.getProtectionDomain().getCodeSource().getLocation().getPath());
private static ArrayList<String> files = new ArrayList<String>();
listFilesForFolder(rootFolder);
@Test(timeout = 1000)
public void testNumberOfSubclasses(){
ArrayList<String> listSubclasses = new ArrayList<>(files);
listSubclasses.removeIf(s -> !s.contains("Superclass.class"));
for(String subclass : listSubclasses){
System.out.println(subclass);
}
assertTrue("You did not create a new subclass!", listSubclasses.size() >1);
}
public static void listFilesForFolder(final File folder) {
for (final File fileEntry : folder.listFiles()) {
if (fileEntry.isDirectory()) {
listFilesForFolder(fileEntry);
} else {
files.add(fileEntry.getName().toString());
}
}
}