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

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

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


当前回答

我知道我在这个派对上晚了几年,但我遇到这个问题是为了解决同样的问题。如果您正在编写Eclipse Plugin(从而利用它们的缓存等),您可以通过编程方式使用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();
    }
  }

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

其他回答

除了你所描述的,没有别的办法了。想想看——如果不扫描类路径上的每个类,如何知道哪些类扩展了ClassX ?

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

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

存在一个项目,它对你的类路径进行必要的扫描和索引,这样你就可以访问这些信息……

反射

Java运行时元数据分析,遵循Scannotations的精神 Reflections扫描您的类路径,索引元数据,允许您在运行时查询它,并可以为项目中的许多模块保存和收集这些信息。 使用Reflections你可以查询你的元数据: 获取某个类型的所有子类型 用一些注释来注释所有类型 获取使用某些注释注释的所有类型,包括注释参数匹配 让所有方法都带有一些注解

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

Try ClassGraph. (Disclaimer, I am the author). ClassGraph supports scanning for subclasses of a given class, either at runtime or at build time, but also much more. ClassGraph can build an abstract representation of the entire class graph (all classes, annotations, methods, method parameters, and fields) in memory, for all classes on the classpath, or for classes in selected packages, and you can query this class graph however you want. ClassGraph supports more classpath specification mechanisms and classloaders than any other scanner, and also works seamlessly with the new JPMS module system, so if you base your code on ClassGraph, your code will be maximally portable. See the API here.

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

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

几年前我就这样做了。最可靠的方法(即使用官方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而不是file。