如何在Java中找到给定类的所有子类(或给定接口的所有实现者)? 到目前为止,我有一个方法来做到这一点,但我发现它相当低效(至少可以说)。 方法是:

获取类路径上存在的所有类名的列表 加载每个类并测试它是否是所需类或接口的子类或实现者

在Eclipse中,有一个很好的特性叫做类型层次结构,它能够非常有效地显示这一点。 如何以编程的方式进行呢?


当前回答

Try ClassGraph. (Disclaimer, I am the author). ClassGraph supports scanning for subclasses of a given class, either at runtime or at build time, but also much more. ClassGraph can build an abstract representation of the entire class graph (all classes, annotations, methods, method parameters, and fields) in memory, for all classes on the classpath, or for classes in selected packages, and you can query this class graph however you want. ClassGraph supports more classpath specification mechanisms and classloaders than any other scanner, and also works seamlessly with the new JPMS module system, so if you base your code on ClassGraph, your code will be maximally portable. See the API here.

其他回答

几年前我就这样做了。最可靠的方法(即使用官方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而不是file。

还要注意的是,这当然只能找到当前类路径中存在的所有子类。想必这对于你目前所看到的是可以的,而且你也有可能考虑过这一点,但如果你在任何时候发布了一个非最终类(对于不同程度的“狂野”),那么其他人已经编写了他们自己的子类,而你不知道这是完全可行的。

Thus if you happened to be wanting to see all subclasses because you want to make a change and are going to see how it affects subclasses' behaviour - then bear in mind the subclasses that you can't see. Ideally all of your non-private methods, and the class itself should be well-documented; make changes according to this documentation without changing the semantics of methods/non-private fields and your changes should be backwards-compatible, for any subclass that followed your definition of the superclass at least.

仅使用内置的Java Reflections API是不可能做到这一点的。

存在一个项目,它对你的类路径进行必要的扫描和索引,这样你就可以访问这些信息……

反射

Java运行时元数据分析,遵循Scannotations的精神 Reflections扫描您的类路径,索引元数据,允许您在运行时查询它,并可以为项目中的许多模块保存和收集这些信息。 使用Reflections你可以查询你的元数据: 获取某个类型的所有子类型 用一些注释来注释所有类型 获取使用某些注释注释的所有类型,包括注释参数匹配 让所有方法都带有一些注解

(免责声明:我没有使用过它,但项目的描述似乎完全符合您的需求。)

找到classpath中的所有类

public static List<String> getClasses() {
        URLClassLoader urlClassLoader = (URLClassLoader) Thread.currentThread().getContextClassLoader();
        List<String> classes = new ArrayList<>();
        for (URL url : urlClassLoader.getURLs()) {
            try {
                if (url.toURI().getScheme().equals("file")) {
                    File file = new File(url.toURI());
                    if (file.exists()) {
                        try {
                            if (file.isDirectory()) {
                                for (File listFile : FileUtils.listFiles(file, new String[]{"class"}, true)) {
                                    String classFile = listFile.getAbsolutePath().replace(file.getAbsolutePath(), "").replace(".class", "");
                                    if (classFile.startsWith(File.separator)) {
                                        classFile = classFile.substring(1);
                                    }
                                    classes.add(classFile.replace(File.separator, "."));
                                }
                            } else {
                                JarFile jarFile = new JarFile(file);
                                if (url.getFile().endsWith(".jar")) {
                                    Enumeration<JarEntry> entries = jarFile.entries();
                                    while (entries.hasMoreElements()) {
                                        JarEntry jarEntry = entries.nextElement();
                                        if (jarEntry.getName().endsWith(".class")) {
                                            classes.add(jarEntry.getName().replace(".class", "").replace("/", "."));
                                        }
                                    }
                                }
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            } catch (URISyntaxException e) {
                e.printStackTrace();
            }
        }
        return classes;
    }

根据您的特定需求,在某些情况下,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.