Как вы находите все подклассы данного класса в Java?


207

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

  1. Получить список всех имен классов, которые существуют на пути к классам
  2. Загрузите каждый класс и проверьте, является ли он подклассом или разработчиком нужного класса или интерфейса.

В Eclipse есть замечательная функция, которая называется Type Hierarchy, которая позволяет показать это довольно эффективно. Как можно это сделать и сделать это программно?


1
Хотя решение, основанное на Reflections и Spring, выглядит интересно, мне нужно было простое решение, которое не имело зависимостей. Кажется, мой оригинальный код (с некоторыми изменениями) был подходящим.
Аврома

1
Конечно, вы можете использовать метод getSupeClass рекурсивно?

Я специально искал все подклассы данного класса. getSuperClass не скажет вам, какие подклассы есть в классе, а только получит непосредственный суперкласс для определенного подкласса. Кроме того, метод isAssignableFrom в классе лучше подходит для того, что вы предлагаете (нет необходимости в рекурсии).
Авром

Этот вопрос связан со многими другими дубликатами, но он не содержит никакого полезного, простого Java-ответа. Вздох ...
Эрик Думинил

Ответы:


77

Нет другого способа сделать это, кроме того, что вы описали. Подумайте об этом - как кто-нибудь может знать, какие классы расширяют ClassX, не сканируя каждый класс на пути к классам?

Eclipse может рассказать вам о супер и подклассах только в то время, которое кажется «эффективным», поскольку в него уже загружены все данные о типах в тот момент, когда вы нажимаете кнопку «Показать в иерархии типов» (поскольку постоянно собирать ваши классы, знает обо всем на пути к классам и т. д.).


23
Теперь есть простая библиотека с именем org.reflections, которая помогает с этой и другими общими задачами отражения. С помощью этой библиотеки вы можете просто позвонить по reflections.getSubTypesOf(aClazz)) ссылке
Enwired

@matt b - если он должен сканировать все классы, означает ли это, что происходит снижение производительности, когда в вашем проекте много классов, хотя лишь немногие из них подклассируют ваш класс?
LeTex,

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

Ответ от fforw работал для меня и должен быть помечен как правильный ответ. Очевидно, это возможно при сканировании пути к классам.
Фаррух Наджми,

Вы ошибаетесь, см. Ответ ниже о библиотеке

127

Сканирование для классов не просто с чистой Java.

Платформа Spring предлагает класс ClassPathScanningCandidateComponentProvider, который может делать то, что вам нужно. В следующем примере все подклассы MyClass будут найдены в пакете org.example.package

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
Значение False должно быть передано в качестве параметра при создании ClassPathScanningCandidateComponentProvider, чтобы отключить фильтры по умолчанию. Фильтры по умолчанию будут соответствовать другим типам классов, например, любым аннотированным @Component. Мы только хотим, чтобы AssignableTypeFilter был активным здесь.
MCDS

Вы говорите, что это нелегко, но как бы мы это сделали, если бы хотели сделать это с чистой Java?
Aequitas

49

Это невозможно сделать, используя только встроенный Java Reflections API.

Существует проект, который выполняет необходимое сканирование и индексацию вашего classpath, чтобы вы могли получить доступ к этой информации ...

Размышления

Анализ метаданных среды выполнения Java в духе Scannotations

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

Используя Reflections, вы можете запросить ваши метаданные для:

  • получить все подтипы некоторого типа
  • получить аннотации всех типов с некоторой аннотацией
  • получить аннотации для всех типов, включая сопоставление параметров аннотации
  • получить аннотации всех методов с некоторыми

(Отказ от ответственности: я не использовал его, но описание проекта, кажется, точно соответствует вашим потребностям.)


1
Интересный. Похоже, что у проекта есть некоторые зависимости, о которых их документация не упоминает. А именно (те, которые я нашел до сих пор): javaassist, log4J, XStream
Avrom

3
Я включил этот проект с Maven, и он работал нормально. Получение подклассов на самом деле является первым примером исходного кода и состоит из двух строк :-)
KarlsFriend

Разве невозможно использовать только встроенный Java Reflections API или просто очень неудобно это делать?
Поток

1
Пожалуйста, будьте осторожны, когда собираетесь использовать Reflections, а затем разверните приложение WAR на GlassFish! В библиотеке Guava существует конфликт, и развертывание завершится с ошибкой развертывания CDI: WELD-001408 - для получения более подробной информации см. GLASSFISH-20579 . FastClasspathScanner является решением в этом случае.
lu_ko

Я просто пробую этот проект, и он работает. Я просто использую его, чтобы улучшить шаблон проектирования стратегии и получить весь конкретный класс стратегии (подкласс), которым я поделюсь с последним демо.
Синь Мэн

10

Не забывайте, что сгенерированный Javadoc для класса будет включать список известных подклассов (и для интерфейсов, известных реализующих классов).


3
это совершенно неверно, суперкласс не должен зависеть от своих подклассов, даже в javadoc или комментариях.
охотник

@ Охотник, я не согласен. Совершенно верно, что JavaDoc включает в себя список известных подклассов. Конечно, «известный» может не включать в себя искомый класс, но для некоторых вариантов использования этого будет достаточно.
Qw3ry

И вы можете пропустить некоторые классы в любом случае: я могу загрузить новый jar-файл в путь к классам (во время выполнения), и каждое обнаружение, которое произошло раньше, завершится неудачей.
Qw3ry

10

Попробуйте ClassGraph . (Отказ от ответственности, я автор). ClassGraph поддерживает сканирование для подклассов данного класса, во время выполнения или во время сборки, но также и многое другое. ClassGraph может построить абстрактное представление всего графа классов (все классы, аннотации, методы, параметры методов и поля) в памяти, для всех классов в пути к классам или для классов в пакетах из белого списка, и вы можете запросить этот класс классов, однако вы хотите. ClassGraph поддерживает больше механизмов спецификации classpath и загрузчиков классов, чем любой другой сканер, а также без проблем работает с новой модульной системой JPMS, поэтому, если вы основываете свой код на ClassGraph, ваш код будет максимально переносимым. Смотрите API здесь.


9

Я сделал это несколько лет назад. Самый надежный способ сделать это (т. Е. С помощью официальных API-интерфейсов Java и без внешних зависимостей) - это написать собственный доклет для создания списка, который можно прочитать во время выполнения.

Вы можете запустить его из командной строки следующим образом:

javadoc -d build -doclet com.example.ObjectListDoclet -sourcepath java/src -subpackages com.example

или запустите его из муравья так:

<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, а не в файл.


Хотя использовать это программно может быть сложно, я должен сказать - разумный подход!
Джанака Бандара

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
ИЛИ, оказывается, вам не нужно делать собственный поиск. Вы можете просто получить ITypeHierarchy непосредственно из своего IType, вызвав для него функцию .newTypeHierarchy (): dev.eclipse.org/newslists/news.eclipse.tools.jdt/msg05036.html
Curtis

7

Принимая во внимание ограничения, упомянутые в других ответах, вы также можете использовать openpojoPojoClassFactory ( доступно на Maven ) следующим образом:

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

Где packageRootнаходится корневая строка пакетов, в которых вы хотите искать (например, "com.mycompany"или просто "com"), и Superclassваш супертип (это работает и на интерфейсах).


Безусловно, самое быстрое и элегантное решение из предложенных.
KidCrippler

4

Я просто пишу простую демонстрацию, чтобы использовать org.reflections.Reflectionsподклассы абстрактного класса:

https://github.com/xmeng1/ReflectionsDemo


было бы более продуктивно, если бы вы разместили здесь пример кода, а не ссылку со множеством классов, которые нужно копать.
ДжессиБойд

4

В зависимости от ваших конкретных требований, в некоторых случаях механизм загрузчика сервисов 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();
}

Класс com.example.ExampleImplреализует этот интерфейс:

package com.example;

public class ExampleImpl implements Example {
    public String getStr() {
        return "ExampleImpl's string.";
    }
}

Вы бы объявили, что класс ExampleImplявляется реализацией Example, создав файл, META-INF/services/com.example.Exampleсодержащий текст com.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 ()) конструктора родительских классов (или создайте класс по умолчанию), но он будет обновляться во время выполнения. Если ленивая инициализация является опцией, вы можете попробовать этот подход.


1

Вы можете использовать библиотеку org.reflections, а затем создать объект класса Reflections. Используя этот объект, вы можете получить список всех подклассов данного класса. https://www.javadoc.io/doc/org.reflections/reflections/0.9.10/org/reflections/Reflections.html

    Reflections reflections = new Reflections("my.project.prefix");
    System.out.println(reflections.getSubTypesOf(A.class)));

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());
        }
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.