我如何枚举包中的所有类并将它们添加到List?

我需要枚举包中的所有类,并将它们添加到列表中。 一个类的非dynamic版本是这样的:

List allClasses = new ArrayList(); allClasses.add(String.class); 

我怎样才能dynamic地添加包中的所有类及其所有子包?


更新:阅读早期的答案,这是绝对正确的,我试图解决另一个次要问题,所以让我说明一下。 而且我知道这是可能的,因为其他工具。 在这里看到新的问题。

更新:再读一遍,我可以看到它是如何被误读的。 编译后,我想从文件系统中枚举所有MY PROJECT'S类。

****更新1(2012)****

好的,我终于清理了下面的代码片段。 我坚持它自己的github项目,甚至增加了testing。

https://github.com/ddopson/java-class-enumerator

****更新2(2016)****

要获得更强大和function更丰富的类path扫描程序,请参阅https://github.com/lukehutch/fast-classpath-scanner/wiki 。 我build议先阅读我的代码片段以获得高层次的理解,然后使用lukehutch的工具进行生产。

****原文(2010)****

严格来说,不可能在一个包中列出这些类。 这是因为一个包实际上只不过是一个名称空间(例如com.epicapplications.foo.bar),并且类path中的任何jar文件都可能将类添加到包中。 更糟糕的是,类加载器将按需加载类,部分类path可能位于networking连接的另一端。

解决一个更严格的问题是可能的。 例如JAR文件中的所有类,或JAR文件在特定包中定义的所有类。 无论如何,这是更常见的情况。

不幸的是,没有任何框架代码来简化这个任务。 您必须以类似ClassLoader查找类定义的方式来扫描文件系统。

网上有很多样本用于纯旧目录中的类文件。 这些天我们大多数人都使用JAR文件。

为了使事情与JAR文件,尝试这…

 private static ArrayList<Class<?>> getClassesForPackage(Package pkg) { String pkgname = pkg.getName(); ArrayList<Class<?>> classes = new ArrayList<Class<?>>(); // Get a File object for the package File directory = null; String fullPath; String relPath = pkgname.replace('.', '/'); System.out.println("ClassDiscovery: Package: " + pkgname + " becomes Path:" + relPath); URL resource = ClassLoader.getSystemClassLoader().getResource(relPath); System.out.println("ClassDiscovery: Resource = " + resource); if (resource == null) { throw new RuntimeException("No resource for " + relPath); } fullPath = resource.getFile(); System.out.println("ClassDiscovery: FullPath = " + resource); try { directory = new File(resource.toURI()); } catch (URISyntaxException e) { throw new RuntimeException(pkgname + " (" + resource + ") does not appear to be a valid URL / URI. Strange, since we got it from the system...", e); } catch (IllegalArgumentException e) { directory = null; } System.out.println("ClassDiscovery: Directory = " + directory); if (directory != null && directory.exists()) { // Get the list of the files contained in the package String[] files = directory.list(); for (int i = 0; i < files.length; i++) { // we are only interested in .class files if (files[i].endsWith(".class")) { // removes the .class extension String className = pkgname + '.' + files[i].substring(0, files[i].length() - 6); System.out.println("ClassDiscovery: className = " + className); try { classes.add(Class.forName(className)); } catch (ClassNotFoundException e) { throw new RuntimeException("ClassNotFoundException loading " + className); } } } } else { try { String jarPath = fullPath.replaceFirst("[.]jar[!].*", ".jar").replaceFirst("file:", ""); JarFile jarFile = new JarFile(jarPath); Enumeration<JarEntry> entries = jarFile.entries(); while(entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); String entryName = entry.getName(); if(entryName.startsWith(relPath) && entryName.length() > (relPath.length() + "/".length())) { System.out.println("ClassDiscovery: JarEntry: " + entryName); String className = entryName.replace('/', '.').replace('\\', '.').replace(".class", ""); System.out.println("ClassDiscovery: className = " + className); try { classes.add(Class.forName(className)); } catch (ClassNotFoundException e) { throw new RuntimeException("ClassNotFoundException loading " + className); } } } } catch (IOException e) { throw new RuntimeException(pkgname + " (" + directory + ") does not appear to be a valid package", e); } } return classes; } 

我想出了如何做到这一点。 程序如下:

  1. 从根包中的一个类开始,从类加载器中获取它所在的文件夹
  2. recursion枚举此文件夹中的所有.class文件
  3. 将文件名称转换为完全限定的类名称
  4. 使用Class.forName()来获取类

这里有一些令人讨厌的诡计让我有些不安,但它有效 – 例如:

  1. 使用string操作将path名转换为包名
  2. 对根包名称进行硬编码以启用剥离path前缀

太糟糕了,stackoverflow不允许我接受我自己的答案…

你可以试试我的库FastClasspathScanner 。

要枚举包中的所有类,请执行以下操作:

 Set<String> classNames = new FastClassPathScanner("com.mypackage") .scan() .getNamesOfAllClasses(); 

恐怕你不得不手动扫描类path和javasearch类的其他地方(例如,ext目录或引导类path)。 由于java使用延迟加载的类,它可能甚至不知道你的包中尚未加载的其他类。 同时检查“密封”包装的概念。

这个问题偶尔会出现,这很有趣。 问题是这个关键字会被命名为“命名空间”。 Java程序包并没有描述一个包含了包中所有类的具体容器。 它只是定义了一个令牌,类可以用来声明它们是该包的成员。 您必须search整个类path(作为另一个答复)才能确定包中的所有类。

这里有一个警告:像Tomcat和JBoss的 ApplicationEngines / servlet容器具有分层类加载器 。 获取系统类加载器将无法做到。

Tomcat的工作方式(事情可能已经改变了,但是我目前的经验并没有让我相信),但是每个应用程序上下文都有它自己的类加载器,所以应用程序“foo”的类不会与应用程序的类fooV2 “

只是一个例子。 如果所有的类都变成了一个超级类的上下文,那么你不知道是否使用了适合版本1或版本2的类。

另外,每个人都需要访问像java.lang.String这样的系统类。 这是层次结构。 它首先检查本地应用程序的上下文,并将其移动(这是我目前的状况)。

为了pipe理这个,更好的方法是: this.getClass()。getClassloader()

在我的情况下,我有一个Web服务需要在一些模块上进行自我发现,他们显然驻留在这个Web服务上下文或系统上下文中。 通过这样做,我可以检查两者。 通过获取系统类加载器,我不能访问任何应用程序类(因此我的资源为空)。

看看java.net.URLClassLoader在做什么。 它从来没有枚举类,它只是试图find类时,要求一个。 如果你想枚举这些类,那么你将需要获得类path,将其分割成目录和jar文件。 扫描目录(及其子目录)和jar文件,查找名称为* .class的文件。

这可能是值得一看的开源项目,似乎做你想要的枚举(如Eclipse )的灵感。

如果你只是想加载一组相关的类,那么Spring可以帮助你。

Spring可以在一行代码中实例化一个实现给定接口的所有类的列表或映射。 列表或映射将包含实现该接口的所有类的实例。

也就是说,作为从文件系统中加载类列表的替代方法,只需要在所有要加载的类中实现相同的接口,而不考虑包。 这样,你可以加载(并实例化)你想要的所有类,而不pipe它们在哪个包中。

另一方面,如果把它们都放在一个包里面,那么只要让这个包中的所有类都实现一个给定的接口即可。