Как я могу перечислять все классы в пакете и добавлять их в список?

Мне нужно перечислить все классы в пакете и добавить их в список. Нединамическая версия для одного класса выглядит следующим образом:

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

Как я могу сделать это динамически, чтобы добавить все классы в пакет и все его подпакеты?


Обновление: Прочитав ранние ответы, абсолютно верно, что я пытаюсь решить другую вторичную проблему, поэтому позвольте мне изложить это. И я знаю, что это возможно, потому что другие инструменты делают это. См. Новый вопрос здесь.

Обновление: Снова прочитав это, я вижу, как это неправильно. Я хочу перечислить все классы MY PROJECT из файловой системы после компиляции.

Ответ 1

**** ОБНОВЛЕНИЕ 1 (2012) ****

ОК, я наконец-то нашел способ очистить фрагмент кода ниже. Я вставил его в собственный проект github и даже добавил тесты.

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

**** ОБНОВЛЕНИЕ 2 (2016) ****

Подробнее о более надежном и многофункциональном сканере путей к классам см. https://github.com/classgraph/classgraph. Я бы рекомендовал сначала прочитать мой фрагмент кода, чтобы лучше понять его, а затем использовать инструмент lukehutch для производственных целей.

**** Оригинальное сообщение (2010) ****

Строго говоря, невозможно перечислить классы в пакете. Это связано с тем, что пакет на самом деле является не чем иным, как пространством имен (например, com.epicapplications.foo.bar), и любой jar файл в пути к классам может потенциально добавлять классы в пакет. Хуже того, загрузчик классов будет загружать классы по требованию, и часть пути к классам может находиться на другой стороне сетевого подключения.

Возможно решить более ограничительную проблему. например, все классы в 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;
}

Ответ 2

Я понял, как это сделать. Здесь процедура:

  • Начните с класса в корневом пакете и получите его папку из загрузчика классов
  • Рекурсивно перечислить все .class файлы в этой папке
  • Преобразование имен файлов в имена классов с полным классом
  • Используйте Class.forName() для получения классов

Здесь есть несколько неприятных трюков, которые меня немного беспокоят, но он работает - например:

  • Преобразование имен путей в имена пакетов с использованием строковых манипуляций
  • Жесткое кодирование имени корневого пакета, чтобы разрешить удаление префикса пути

Слишком плохо, что stackoverflow не позволяет мне принять мой собственный ответ...

Ответ 3

В настоящее время наиболее надежным механизмом для перечисления всех классов в данном пакете является ClassGraph, поскольку он обрабатывает максимально широкий массив механизмов спецификации пути к классам, включая новую систему модулей JPMS. (Я автор.)

List<String> classNames;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("my.package")
        .enableClassInfo().scan()) {
    classNames = scanResult.getAllClasses().getNames();
}

Ответ 4

Я боюсь, вам придется вручную проверять путь к классам и другие места, где java ищет классы (например, каталог ext или путь к классу загрузки). Поскольку java использует ленивую загрузку классов, он может даже не знать о дополнительных классах в ваших пакетах, которые еще не загружены. Также проверьте понятие "запечатанных" пакетов.

Ответ 5

Забавно, что этот вопрос возникает раз в то время. Проблема в том, что это ключевое слово было бы более правильно названо "пространство имен". Пакет Java не определяет конкретный контейнер, который в любой момент содержит все классы в пакете. Он просто определяет токен, который классы могут использовать, чтобы объявить, что они являются частью этого пакета. Вам нужно будет просмотреть весь путь к классу (как указывал другой ответ), чтобы определить все классы в пакете.

Ответ 6

Здесь есть предостережение: контейнеры ApplicationEngines/servlet, такие как tomcat и JBoss, имеют иерархические загрузчики классов. Получение загрузчика системного класса не будет.

Как работает Tomcat (все может измениться, но мой текущий опыт не заставил меня поверить иначе), но каждый контекст приложения имеет свой собственный загрузчик классов, так что классы для приложения 'foo' не сталкивайтесь с классами для приложения 'fooV2'

Как пример. Если все классы попали в один контекст класса uber, тогда вы не имели бы понятия, используете ли вы классы, подходящие для версии 1 или версии 2.

Кроме того, каждый из них нуждается в доступе к системным классам, таким как java.lang.String. Это иерархия. Сначала он проверяет контекст локального приложения и перемещает его вверх (это моя текущая ситуация BTW).

Для управления этим лучшим подходом будет: this.getClass(). getClassloader()

В моем случае у меня есть веб-сервис, который должен выполнять самообнаружение на некоторых модулях, и они, очевидно, находятся в контексте 'this' webservice или в системном контексте. Делая вышеизложенное, я могу проверить оба. Просто получая системный загрузчик классов, я не получаю доступ к любому из классов приложений (и, следовательно, мои ресурсы равны нулю).

Ответ 7

Посмотрите, что делает java.net.URLClassLoader. Он никогда не перечисляет классы, он просто пытается найти классы, когда их просят. Если вы хотите перечислить классы, вам нужно будет получить путь к классам, разделить его на каталоги и файлы jar. Сканирование каталогов (и их подкаталогов) и файлов jar для файлов с именем *.class.

Возможно, стоит посмотреть проекты с открытым исходным кодом, которые, как представляется, делают нужный вам перечисление (например Eclipse) для вдохновения.

Ответ 8

Если вы просто хотите загрузить группу связанных классов, то Spring может вам помочь.

Spring может создавать список или карту всех классов, реализующих данный интерфейс в одной строке кода. Список или карта будут содержать экземпляры всех классов, реализующих этот интерфейс.

Как говорится, в качестве альтернативы загрузке списка классов из файловой системы вместо этого следует реализовать один и тот же интерфейс во всех классах, которые вы хотите загрузить, независимо от пакета. Таким образом, вы можете загружать (и создавать экземпляры) все классы, которые вы хотите, независимо от того, в каком пакете они находятся.

С другой стороны, если их все в пакете - это то, что вы хотите, просто попросите все классы в этом пакете реализовать данный интерфейс.