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

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

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

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

Ответ 1

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

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

Ответ 2

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

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

  ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider (false);
provider.addIncludeFilter(новый AssignableTypeFilter (MyClass.class));

// сканирование в org.example.package
Set <BeanDefinition> components = provider.findCandidateComponents( "org/example/package" );
для компонента BeanDefinition: компоненты)
{   Класс cls = Class.forName(component.getBeanClassName());   // используем класс cls found
}
Код>

Этот метод имеет дополнительное преимущество использования анализатора байт-кода для поиска кандидатов, что означает, что он не загружает все классы, которые он сканирует.

Ответ 3

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

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

Reflections

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

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

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

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

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

Ответ 4

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

Ответ 5

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

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

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

Ответ 6

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

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

Ответ 7

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

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

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

Ответ 8

Следует также отметить, что это, конечно же, найдет только те подклассы, которые существуют в вашем текущем пути к классам. Предположительно, это нормально для того, что вы сейчас просматриваете, и, скорее всего, вы это рассмотрели, но если в какой-то момент вы выпустили класс не final в дикий (для разных уровней "дикий" ), то это вполне возможно, что кто-то другой написал свой собственный подкласс, о котором вы не узнаете.

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

Ответ 9

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

Ответ 10

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

Ответ 11

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

https://github.com/xmeng1/ReflectionsDemo

Ответ 12

Я использую отражение lib, которое сканирует ваш путь к классам для всех подклассов: https://github.com/ronmamo/reflections

Вот как это было бы сделано:

Reflections reflections = new Reflections("my.project");
Set<Class<? extends SomeType>> subTypes = reflections.getSubTypesOf(SomeType.class);

Ответ 13

В зависимости от ваших конкретных требований, в некоторых случаях механизм Java service loader может достичь того, что вам нужно.

Вкратце, он позволяет разработчикам явно объявлять, что класс подклассифицирует какой-либо другой класс (или реализует некоторый интерфейс), перечислив его в файл в каталоге JAR/WAR META-INF/services. Затем он может быть обнаружен с помощью класса java.util.ServiceLoader, который при задании объекта Class будет генерировать экземпляры всех объявленных подклассов этот класс (или, если Class представляет собой интерфейс, все классы, реализующие этот интерфейс).

Основное преимущество этого подхода состоит в том, что нет необходимости вручную проверять весь путь к классам для подклассов - вся логика обнаружения содержится в классе ServiceLoader и загружает только классы, явно объявленные в META-INF/services каталог (не каждый класс в пути к классам).

Существуют, однако, некоторые недостатки:

  • Он не найдет подклассы all, только те, которые явно объявлены. Таким образом, если вам действительно нужно найти все подклассы, этот подход может быть недостаточным.
  • Это требует, чтобы разработчик явно объявлял класс в каталоге META-INF/services. Это дополнительное бремя для разработчика и может быть подвержено ошибкам.
  • ServiceLoader.iterator() генерирует экземпляры подкласса, а не их объекты Class. Это вызывает два вопроса:
    • Вы не можете сказать, как строятся подклассы - конструктор no-arg используется для создания экземпляров.
    • Таким образом, подклассы должны иметь конструктор по умолчанию, или должны быть объяснены конструктором no-arg.

Очевидно, что 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 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 string.", plus whatever is returned
// by other declared implementations of com.example.Example.

Ответ 14

Добавьте их в статическую карту внутри (this.getClass(). getName()) конструктор родительских классов (или создайте по умолчанию), но это будет обновляться во время выполнения. Если ленивая инициализация - это вариант, вы можете попробовать этот подход.

Ответ 15

Мне нужно было сделать это в качестве тестового примера, чтобы узнать, добавлены ли новые классы в код. Это то, что я сделал

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