如何在Java中找到给定类的所有子类?

时间:2009-01-29 15:53:03

标签: java class interface subclass

如何在Java中尝试查找给定类(或给定接口的所有实现者)的所有子类? 截至目前,我有一种方法可以做到这一点,但我觉得效率很低(至少可以说)。 方法是:

  1. 获取类路径中存在的所有类名的列表
  2. 加载每个类并测试它是否是所需类或接口的子类或实现者
  3. 在Eclipse中,有一个很好的功能叫做类型层次结构,可以非常有效地显示它。 如何进行编程并以编程方式执行?

18 个答案:

答案 0 :(得分:118)

使用纯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
}

此方法的另一个好处是使用字节码分析器查找候选项,这意味着它加载它扫描的所有类。

答案 1 :(得分:69)

除了你描述的内容之外别无他法。想一想 - 如何在不扫描类路径上的每个类的情况下知道什么类扩展ClassX?

Eclipse只能告诉你超级和子类似乎是一个“有效”的时间,因为它已经在你按下“在类型层次结构中显示”按钮的位置加载了所有类型数据(因为它不断编译你的类,知道类路径上的所有内容,等等。)

答案 2 :(得分:46)

仅使用内置的Java Reflections API无法做到这一点。

存在一个项目,它对您的类路径进行必要的扫描和索引,以便您可以访问此信息...

Reflections

本着Scannotations

的精神进行Java运行时元数据分析 Reflections会扫描您的类路径,索引元数据,允许您在运行时查询它,并可以为项目中的许多模块保存和收集该信息。

使用Reflections,您可以查询元数据:

  • 获取某种类型的所有子类型
  • 获取所有注释了一些注释的类型
  • 获取所有注释了一些注释的类型,包括匹配的注释参数
  • 获取所有使用某些
  • 注释的方法

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

答案 3 :(得分:10)

不要忘记,为类生成的Javadoc将包含已知子类的列表(以及接口,已知的实现类)。

答案 4 :(得分:8)

几年前我这样做了。执行此操作的最可靠方法(即使用官方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而不是文件。

答案 5 :(得分:7)

我知道我参加这个聚会已经晚了几年,但我遇到了这个试图解决同样问题的问题。您可以以编程方式使用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();
    }
  }

我到目前为止看到的第一个问题是我只捕获直接实现接口的类,而不是所有子类 - 但是一点点递归从不会伤害任何人。

答案 6 :(得分:7)

请记住其他答案中提到的限制,您也可以通过以下方式使用openpojo's PojoClassFactoryavailable on Maven):

for(PojoClass pojoClass : PojoClassFactory.enumerateClassesByExtendingType(packageRoot, Superclass.class, null)) {
    System.out.println(pojoClass.getClazz());
}

其中packageRoot是您要搜索的包的根字符串(例如"com.mycompany"或甚至只是"com"),Superclass是您的超类型(这是有效的)在接口上也是如此)。

答案 7 :(得分:6)

试试ClassGraph。 (免责声明,我是作者)。 ClassGraph支持在运行时或构建时扫描给定类的子类,但也支持更多。 ClassGraph可以在内存中为类路径上的所有类或白名单包中的类构建整个类图(所有类,注释,方法,方法参数和字段)的抽象表示,然后您可以查询该类图你要。 ClassGraph比任何其他扫描程序都支持more classpath specification mechanisms and classloaders,并且还可以与新的JPMS模块系统无缝协作,因此如果您将代码基于ClassGraph,您的代码将最大程度地可移植。 See the API here.

答案 8 :(得分:3)

应该注意的是,这当然只能找到当前类路径中存在的所有子类。大概这对你目前正在看的东西是可以的,你可能会考虑到这一点,但如果你在任何时候发布了一个非final类到野外(对于不同级别的“野性”)那么其他人编写了你自己不了解的子类是完全可行的。

因此,如果您碰巧想要查看所有子类,因为您想要进行更改并且要查看它如何影响子类的行为 - 那么请记住您看不到的子类。理想情况下,所有非私有方法和类本身都应该有详细记录;根据本文档进行更改而不更改方法/非私有字段的语义,并且对于任何至少遵循超类定义的子类,您的更改应该是向后兼容的。

答案 9 :(得分:3)

您看到实现与Eclipse之间存在差异的原因是因为您每次扫描,而Eclipse(和其他工具)只扫描一次(在项目加载期间大多数时间)并创建索引。下次您要求数据时,它不会再次扫描,但请查看索引。

答案 10 :(得分:3)

我只是编写一个简单的演示来使用HtmlControl control = Master.FindControl("nbGallery") as HtmlControl; control.Attributes.Add("class", "active"); 来获取抽象类的子类:

https://github.com/xmeng1/ReflectionsDemo

答案 11 :(得分:2)

我使用了一个反射库,它会扫描你的类路径中的所有子类:https://github.com/ronmamo/reflections

这是如何做到的:

Reflections reflections = new Reflections("my.project");
Set<Class<? extends SomeType>> subTypes = reflections.getSubTypesOf(SomeType.class);

答案 12 :(得分:2)

根据您的特定要求,在某些情况下,Java service loader机制可能会实现您的目标。

简而言之,它允许开发人员通过将其列在JAR / WAR文件的META-INF/services目录中的文件中来显式声明某个类是否为其他类(或实现某个接口)的子类。然后可以使用java.util.ServiceLoader类来发现它,当给定Class对象时,它将生成该类的所有已声明子类的实例(或者,如果Class表示接口,所有实现该接口的类。)

这种方法的主要优点是不需要为子类手动扫描整个类路径 - 所有发现逻辑都包含在ServiceLoader类中,并且它只加载META-INF/services中显式声明的类。 {1}}目录(不是类路径上的每个类)。

然而,有一些缺点:

  • 它找不到所有子类,只有那些明确声明的子类。因此,如果您需要真正找到所有子类,这种方法可能是不够的。
  • 它要求开发人员在META-INF/services目录下显式声明该类。这对开发人员来说是一个额外的负担,并且可能容易出错。
  • ServiceLoader.iterator()生成子类实例,而不是Class个对象。这导致两个问题:
    • 你对如何构造子类没有任何发言权 - 使用no-arg构造函数来创建实例。
    • 因此,子类必须具有默认构造函数,或者必须明确声明一个无参数构造函数。

显然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.";
    }
}

您可以通过创建包含文字ExampleImpl的文件Example来声明课程META-INF/services/com.example.Examplecom.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.

答案 13 :(得分:1)

将它们添加到静态地图里面(this.getClass()。getName())父类构造函数(或创建一个默认值),但这将在运行时更新。如果延迟初始化是一个选项,您可以尝试这种方法。

答案 14 :(得分:1)

您可以使用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)));

答案 15 :(得分:0)

我需要将此作为测试用例,以查看是否已将新类添加到代码中。这就是我所做的

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());
        }
    }
}

答案 16 :(得分:0)

如果打算加载同一包中给定类的所有子类,则可以这样做:

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

答案 17 :(得分:0)

查找类路径中的所有类

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