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

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

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


当前回答

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

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.

其他回答

不要忘记,为类生成的Javadoc将包括一个已知子类列表(对于接口,包括已知实现类)。

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

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扫描类并不容易。

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
}

该方法还有一个额外的好处,就是使用字节码分析器来查找候选类,这意味着它不会加载所扫描的所有类。

找到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;
    }

您看到您的实现和Eclipse之间的区别的原因是您每次都扫描,而Eclipse(和其他工具)只扫描一次(大多数时候在项目加载期间)并创建索引。下次你请求数据时,它不再扫描,而是查看索引。