【问题标题】:How do you find all subclasses of a given class in Java?如何在 Java 中找到给定类的所有子类?
【发布时间】:2010-10-04 06:42:26
【问题描述】:

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

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

在 Eclipse 中,有一个很好的特性,称为类型层次结构,它可以非常有效地显示这一点。 如何以编程方式进行操作?

【问题讨论】:

  • 虽然基于 Reflections 和 Spring 的解决方案看起来很有趣,但我需要一些没有依赖关系的简单解决方案。看来我的原始代码(经过一些调整)是可行的方法。
  • 你确定可以递归使用getSupeClass方法吗?
  • 我专门寻找给定类的所有子类。 getSuperClass 不会告诉你一个类有哪些子类,只会获取特定子类的直接超类。此外,方法 isAssignableFrom on Class 更适合您的建议(无需递归)。
  • 这个问题与许多其他重复问题相关联,但它不包含任何有用的、简单的 Java 答案。叹息……

标签: java class interface subclass


【解决方案1】:

除了您描述的以外,没有其他方法可以做到这一点。想一想 - 如果不扫描类路径上的每个类,谁能知道哪些类扩展了 ClassX?

Eclipse 只能在看似“有效”的时间内告诉您有关超类和子类的信息,因为它已经在您按下“在类型层次结构中显示”按钮时加载了所有类型数据(因为它不断地编译你的类,知道类路径上的所有东西,等等)。

【讨论】:

  • 现在有一个名为org.reflections 的简单库可以帮助完成这个和其他常见的反射任务。有了这个库,你只需调用reflections.getSubTypesOf(aClazz))link
  • @matt b - 如果它必须扫描所有类,这是否意味着当您的项目中有很多类时性能会下降,即使其中只有少数是子类给你上课?
  • 没错。它只涉及所有课程。您可以定义自己的扫描仪,您可以加快它的速度,例如排除某些您知道您的类不会被扩展的包,或者只是打开类文件并检查在类的常量部分中是否找到类名,避免让反射扫描仪读取有关甚至不包含对(直接)超类的必需引用的类的更多信息。间接地,您需要进一步扫描。所以它是目前最好的。
  • fforw 的答案对我有用,应该标记为正确答案。显然,这可以通过类路径扫描来实现。
  • 你错了,看下面关于 Burningwave 库的回复
【解决方案2】:

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

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

Reflections

本着 Scannotations 精神的 Java 运行时元数据分析

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

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

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

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

【讨论】:

  • 有趣。该项目似乎有一些依赖项,他们的文档似乎没有提及。即(到目前为止我发现的那些):javaassist、log4J、XStream
  • 我将这个项目包含在 maven 中,它运行良好。获取子类实际上是第一个源代码示例,有两行长:-)
  • 只使用内置的 Java Reflections API 是不可能的,还是这样做非常不方便?
  • 当您要使用 Reflections 并将您的应用 WAR 部署到 GlassFish 时,请务必小心! Guava 库中存在冲突,部署将失败并出现错误 CDI 部署失败:WELD-001408 - 请参阅GLASSFISH-20579 了解更多详细信息。 FastClasspathScanner 在这种情况下是一个解决方案。
  • 我只是尝试这个项目并且它有效。我只是用它来增强策略设计模式并获取所有策略具体类(子类)我将在后面分享演示。
【解决方案3】:

还应该注意的是,这当然只会找到当前类路径中存在的所有子类。大概这对于您当前正在查看的内容是可以的,并且您可能确实考虑过这一点,但是如果您在任何时候将非final 类发布到野外(对于不同级别的“野生”),那么它是完全可行的是,其他人编写了他们自己的子类,而你不会知道。

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

【讨论】:

    【解决方案4】:

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

    【讨论】:

    • 这是完全错误的,超类不应该依赖于它们的子类,甚至在javadoc或comment中也不应该。
    • @hunter 我不同意。 JavaDoc 包含 known 子类的列表是完全正确的。当然,“已知”可能不包括您要查找的类,但对于某些用例来说就足够了。
    • 在任何情况下你都可能会错过一些类:我可以将一个新的 jar 加载到类路径中(在运行时),之前发生的每个检测都会失败。
    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

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

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

      【讨论】:

      • False 在创建 ClassPathScanningCandidateComponentProvider 时应作为参数传入以禁用默认过滤器。默认过滤器将匹配其他类型的类,例如任何用@Component 注释的东西。我们只希望 AssignableTypeFilter 在此处处于活动状态。
      • 你说不容易,但是如果我们想用纯java来做呢?
      【解决方案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();
          }
        }
      

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

      【讨论】:

      • 或者,事实证明您不必自己进行搜索。您可以通过调用 .newTypeHierarchy() 直接从您的 IType 获取 ITypeHierarchy:dev.eclipse.org/newslists/news.eclipse.tools.jdt/msg05036.html
      • 我什至更晚了...... OP 只提到了 Eclipse,因为 Eclipse 做了他想做的事,他的“以编程方式”声明将 Eclipse 作为解决方案的一部分排除在外。
      【解决方案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 而不是文件。

      【讨论】:

      • 虽然以编程方式使用它可能会很棘手,但我必须说 - 一种 智能 方法!
      【解决方案9】:

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

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

      packageRoot 是您希望在其中搜索的包的根字符串(例如 "com.mycompany" 甚至只是 "com"),Superclass 是您的超类型(这也适用于接口)。

      【讨论】:

      • 目前建议中最快、最优雅的解决方案。
      【解决方案10】:

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

      【讨论】:

        【解决方案11】:

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

        【讨论】:

          【解决方案12】:

          我只是写了一个简单的demo,使用org.reflections.Reflections获取抽象类的子类:

          https://github.com/xmeng1/ReflectionsDemo

          【讨论】:

          • 如果你在这里发布一个代码示例而不是一个包含很多类要挖掘的链接会更有效率。
          【解决方案13】:

          我正在使用反射库,它会扫描您的类路径以查找所有子类:https://github.com/ronmamo/reflections

          这是怎么做的:

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

          【讨论】:

            【解决方案14】:

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

            简而言之,它允许开发人员通过在 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.";
                }
            }
            

            您可以通过创建包含文本com.example.ExampleImpl 的文件META-INF/services/com.example.Example 来声明类ExampleImplExample 的实现。

            然后,您可以获得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.
            

            【讨论】:

              【解决方案15】:

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

              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】:

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

                【讨论】:

                  【解决方案17】:

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

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

                  【讨论】:

                    【解决方案18】:

                    查找类路径中的所有类

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

                    【讨论】:

                      猜你喜欢
                      • 2011-04-21
                      • 1970-01-01
                      • 2010-10-16
                      • 1970-01-01
                      相关资源
                      最近更新 更多