如何在Java中找到给定类的所有子类(或给定接口的所有实现者)? 到目前为止,我有一个方法来做到这一点,但我发现它相当低效(至少可以说)。 方法是:
获取类路径上存在的所有类名的列表 加载每个类并测试它是否是所需类或接口的子类或实现者
在Eclipse中,有一个很好的特性叫做类型层次结构,它能够非常有效地显示这一点。 如何以编程的方式进行呢?
如何在Java中找到给定类的所有子类(或给定接口的所有实现者)? 到目前为止,我有一个方法来做到这一点,但我发现它相当低效(至少可以说)。 方法是:
获取类路径上存在的所有类名的列表 加载每个类并测试它是否是所需类或接口的子类或实现者
在Eclipse中,有一个很好的特性叫做类型层次结构,它能够非常有效地显示这一点。 如何以编程的方式进行呢?
当前回答
根据您的特定需求,在某些情况下,Java的服务加载器机制可能实现您想要的结果。
简而言之,它允许开发人员通过将一个类列在JAR/WAR文件的META-INF/services目录中的一个文件中,显式地声明一个类是另一个类的子类(或实现了一些接口)。然后可以使用java.util.ServiceLoader类发现它,当给出class对象时,它将生成该类的所有声明子类的实例(或者,如果class表示接口,则生成实现该接口的所有类)。
这种方法的主要优点是不需要手动扫描整个类路径中的子类——所有的发现逻辑都包含在ServiceLoader类中,它只加载在META-INF/services目录中显式声明的类(而不是类路径中的每个类)。
然而,也有一些缺点:
It won't find all subclasses, only those that are explicitly declared. As such, if you need to truly find all subclasses, this approach may be insufficient. It requires the developer to explicitly declare the class under the META-INF/services directory. This is an additional burden on the developer, and can be error-prone. The ServiceLoader.iterator() generates subclass instances, not their Class objects. This causes two issues: You don't get any say on how the subclasses are constructed - the no-arg constructor is used to create the instances. As such, the subclasses must have a default constructor, or must explicity declare a no-arg constructor.
显然,Java 9将解决其中一些缺点(特别是关于子类实例化的缺点)。
一个例子
假设你对查找实现接口com.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.";
}
}
通过创建文件META-INF/services/com.example,可以声明类ExampleImpl是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.
其他回答
你可以使用org。然后,创建一个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)));
除了你所描述的,没有别的办法了。想想看——如果不扫描类路径上的每个类,如何知道哪些类扩展了ClassX ?
Eclipse只能在看起来“有效”的时间内告诉您关于父类和子类的信息,因为在您按下“显示在类型层次结构中”按钮时,它已经加载了所有类型数据(因为它一直在编译您的类,知道类路径上的所有内容,等等)。
我需要将此作为一个测试用例,以查看是否向代码中添加了新的类。这就是我所做的
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());
}
}
}
不要忘记,为类生成的Javadoc将包括一个已知子类列表(对于接口,包括已知实现类)。
如果你想加载给定类在同一个包中的所有子类,你可以这样做:
public static List<Class> loadAllSubClasses(Class pClazz) throws IOException, ClassNotFoundException {
ClassLoader classLoader = pClazz.getClassLoader();
assert classLoader != null;
String packageName = pClazz.getPackage().getName();
String dirPath = packageName.replace(".", "/");
Enumeration<URL> srcList = classLoader.getResources(dirPath);
List<Class> subClassList = new ArrayList<>();
while (srcList.hasMoreElements()) {
File dirFile = new File(srcList.nextElement().getFile());
File[] files = dirFile.listFiles();
if (files != null) {
for (File file : files) {
String subClassName = packageName + '.' + file.getName().substring(0, file.getName().length() - 6);
if (! subClassName.equals(pClazz.getName())) {
subClassList.add(Class.forName(subClassName));
}
}
}
}
return subClassList;
}