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

Avr*_*rom 194 java interface class subclass

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

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

在Eclipse中,有一个很好的功能叫做类型层次结构,可以非常有效地显示它.如何以编程方式进行操作?

ffo*_*orw 122

使用纯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
}
Run Code Online (Sandbox Code Playgroud)

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

  • 在创建ClassPathScanningCandidateComponentProvider以禁用默认过滤器时,应将False作为参数传入.默认过滤器将匹配其他类型的类,例如使用@Component注释的任何类.我们只希望AssignableTypeFilter在这里处于活动状态. (20认同)

mat*_*t b 69

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

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

  • 现在有一个名为[org.reflections](https://github.com/ronmamo/reflections)的简单库,它可以帮助完成这个和其他常见的反射任务.使用这个库你可以调用`reflections.getSubTypesOf(aClazz))`[link](https://github.com/ronmamo/reflections) (20认同)

Mar*_*ouf 47

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

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

思考

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

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

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

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

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

  • 有趣的。该项目似乎有一些依赖项,他们的文档似乎没有提及。即(我到目前为止找到的):javaassist、log4J、XStream (2认同)
  • 我把这个项目包含在maven中并且运行良好.获取子类实际上是第一个源代码示例,并且是两行长:-) (2认同)
  • org.reflections 维护得不是很好(没有定期发布),有很多未解决的问题和拉取请求。更好地使用 ClassGraph 或 Springs ClassPathScanningCandidateComponentProvider (2认同)

Rob*_*Rob 10

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

  • 这是完全错误的,超类不应该依赖于他们的子类,甚至不是javadoc或comment. (3认同)

Dav*_*pik 8

几年前我这样做了.执行此操作的最可靠方法(即使用官方Java API且无外部依赖项)是编写自定义doclet以生成可在运行时读取的列表.

您可以从命令行运行它,如下所示:

javadoc -d build -doclet com.example.ObjectListDoclet -sourcepath java/src -subpackages com.example
Run Code Online (Sandbox Code Playgroud)

或者像这样从ant运行它:

<javadoc sourcepath="${src}" packagenames="*" >
  <doclet name="com.example.ObjectListDoclet" path="${build}"/>
</javadoc>
Run Code Online (Sandbox Code Playgroud)

这是基本代码:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

为简单起见,我删除了命令行参数解析,并且我正在写入System.out而不是文件.


Cur*_*tis 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();
    }
  }
Run Code Online (Sandbox Code Playgroud)

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

  • 或者,事实证明您不必进行自己的搜索.您可以通过调用.newTypeHierarchy()直接从您的IType获取ITypeHierarchy:http://dev.eclipse.org/newslists/news.eclipse.tools.jdt/msg05036.html (3认同)

mik*_*łak 7

请记住其他答案中提到的限制,您也可以通过以下方式使用openpojoPojoClassFactory(在Maven上可用):

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

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


Luk*_*son 7

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


Mac*_*Mac 6

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

简而言之,它允许开发人员通过在 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();
}
Run Code Online (Sandbox Code Playgroud)

该类com.example.ExampleImpl实现该接口:

package com.example;

public class ExampleImpl implements Example {
    public String getStr() {
        return "ExampleImpl's string.";
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以通过创建包含文本的文件来声明该类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.
Run Code Online (Sandbox Code Playgroud)