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


207

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

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

在Eclipse中,有一个很好的功能,称为类型层次结构(Type Hierarchy),可以很有效地显示这一点。如何进行编程?


1
尽管基于Reflections和Spring的解决方案看起来很有趣,但是我需要一些没有依赖性的简单解决方案。看来我的原始代码(有些修改)是正确的方法。
Avrom,2009年

1
您肯定可以递归使用getSupeClass方法吗?

我专门在寻找给定类的所有子类。getSuperClass不会告诉您某个类具有哪些子类,而只会获取特定子类的直接超类。同样,类上的isAssignableFrom方法更适合您的建议(无需递归)。
Avrom

这个问题是与许多其他重复项链接的,但是它不包含任何有用的简单Java答案。感叹……
Eric Duminil

Answers:


77

除了您所描述的以外,没有其他方法可以做到。想想看-谁能在不扫描类路径上每个类的情况下知道哪些类扩展了ClassX?

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


23
现在有一个名为org.reflections的简单库,可以帮助完成此任务和其他常见的反射任务。有了这个图书馆,您只需致电reflections.getSubTypesOf(aClazz)) 链接
连线

@matt b-如果必须扫描所有类,这是否意味着即使项目中只有很少的子类继承了您的项目中的许多类,性能也会降低?
LeTex 2015年

究竟。它只涉及所有课程。您可以定义自己的扫描仪,也可以加快扫描仪的速度,例如排除某些不会扩展您的类的包,或者只是打开类文件并检查类的常量部分中的类名称,避免让反射扫描仪读取关于甚至不包含对(直接)超类的必需引用的类的更多信息间接地,您需要进一步扫描。因此,这是目前最好的。
Martin Kersten

fforw的答案对我有用,应标记为正确答案。显然,使用类路径扫描是可能的。
Farrukh Najmi

您错了,请参阅以下有关Burningwave库的响应

127

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

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


23
创建ClassPathScanningCandidateComponentProvider来禁用默认过滤器时,应将False作为参数传递。默认过滤器将匹配其他类型的类,例如用@Component注释的任何类。我们只希望AssignableTypeFilter在这里处于活动状态。
MCDS 2013年

您说这并不容易,但是如果我们想使用纯Java来做到这一点,我们将如何做呢?
Aequitas

49

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

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

感言

遵循Scannotations精神的Java运行时元数据分析

Reflections扫描您的类路径,为元数据建立索引,使您可以在运行时对其进行查询,并可以保存和收集项目中许多模块的信息。

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

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

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


1
有趣。该项目似乎有一些依赖关系,而其文档似乎并未提及。即(我到目前为止发现的):javaassist,log4J,XStream
Avrom

3
我在Maven中加入了这个项目,并且效果很好。获取子类实际上是第一个源代码示例,并且只有两行:-)
KarlsFriend,2012年

不能仅使用内置的Java Reflections API还是这样做非常不方便?
流动

1
当您要使用Reflections,然后将您的应用程序WAR部署到GlassFish时,请小心!Guava库中存在冲突,并且部署将因错误的CDI部署失败而失败:WELD-001408-有关更多详细信息,请参阅GLASSFISH-20579。在这种情况下,FastClasspathScanner是一种解决方案。
lu_ko '16

我只是尝试这个项目而已。我只是用它来增强策略设计模式,并获得所有策略具体类(子类),我将在后面共享演示。
新梦

10

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


3
这是完全不正确的,超类也不应该依赖于它们的子类,即使在javadoc或注释中也是如此。
猎人

@猎人我不同意。JavaDoc包含已知子类列表是完全正确的。当然,“已知”可能不包括您要查找的类,但是对于某些用例,它就足够了。
Qw3ry

在任何情况下,您都可能会错过一些类:我可以将一个新的jar加载到类路径中(在运行时),并且之前发生的每个检测都将失败。
Qw3ry

10

尝试ClassGraph。(免责声明,我是作者)。ClassGraph支持在运行时或构建时扫描给定类的子类,但还可以进行更多扫描。ClassGraph可以在内存中,类路径上的所有类或白名单包中的类中构建整个类图(所有类,批注,方法,方法参数和字段)的抽象表示,但是您可以查询该类图你要。其他任何扫描仪相比,ClassGraph支持更多的类路径规范机制和类加载器,并且还可以与新的JPMS模块系统无缝协作,因此,如果您的代码基于ClassGraph,则代码将具有最大的可移植性。请参阅此处的API。


9

我几年前做了。最可靠的方法(即使用正式的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而不是文件。


虽然必须以编程方式使用它可能很棘手,但我必须说-一种聪明的方法!
Janaka Bandara

8

我知道我参加这个聚会已经晚了几年,但是我遇到了这个问题,试图解决同样的问题。如果要编写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();
    }
  }

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


3
或者,事实证明您不必自己进行搜索。您可以直接通过在其IType
Curtis

7

记住其他答案中提到的限制,您还可以按以下方式使用openpojoPojoClassFactory在Maven上可用):

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

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


到目前为止,建议的解决方案是最快,最优雅的解决方案。
KidCrippler '16


4

根据您的特定要求,在某些情况下,Java的服务加载程序机制可能会实现您所追求的目标。

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

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

但是,有一些缺点:

  • 它不会找到所有子类,仅会找到那些明确声明的子类。因此,如果您需要真正找到所有子类,则此方法可能不足。
  • 它要求开发人员在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.";
    }
}

您将通过创建一个包含text 的文件来声明该类ExampleImpl是的实现。ExampleMETA-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.

3

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

因此,如果您由于要进行更改而恰好想要查看所有子类,并想知道它如何影响子类的行为-请记住您看不到的子类。理想情况下,所有非私有方法以及类本身都应有充分的文档记录;在不更改方法/非私有字段的语义的情况下,根据本文档进行更改,并且对于至少遵循超类定义的任何子类,您的更改应向后兼容。


3

之所以看到实现与Eclipse之间存在差异,是因为您每次都进行扫描,而Eclipse(和其他工具)仅扫描一次(在大多数项目加载过程中)并创建索引。下次您请求数据时,它不会再次扫描,而是查看索引。


3

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

这是这样做的:

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

2

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



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());
        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.