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

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

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

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

19 ответов

Решение

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

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

Сканирование для классов не просто с чистой 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
}

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

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

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

Размышления

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

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

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

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

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

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

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

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

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

Я сделал это несколько лет назад. Самый надежный способ сделать это (т. Е. С помощью официальных 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, а не в файл.

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

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

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

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

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

https://github.com/xmeng1/ReflectionsDemo

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

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

Вы можете использовать библиотеку 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)));

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

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

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

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

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

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

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

Если вы собираетесь загрузить все подклассы данного класса, находящиеся в одном пакете, вы можете сделать это:

      public static List<Class> loadAllSubClasses(Class pClazz) throws IOException, ClassNotFoundException {
    ClassLoader classLoader = pClazz.getClassLoader();
    assert classLoader != null;
    String packageName = pClazz.getPackage().getName();
    String dirPath = packageName.replace(".", "/");
    Enumeration<URL> srcList = classLoader.getResources(dirPath);

    List<Class> subClassList = new ArrayList<>();
    while (srcList.hasMoreElements()) {
        File dirFile = new File(srcList.nextElement().getFile());
        File[] files = dirFile.listFiles();
        if (files != null) {
            for (File file : files) {
                String subClassName = packageName + '.' + file.getName().substring(0, file.getName().length() - 6);
                if (! subClassName.equals(pClazz.getName())) {
                    subClassList.add(Class.forName(subClassName));
                }
            }
        }
    }

    return subClassList;
}

найти все классы в пути к классам

      public static List<String> getClasses() {
        URLClassLoader urlClassLoader = (URLClassLoader) Thread.currentThread().getContextClassLoader();
        List<String> classes = new ArrayList<>();
        for (URL url : urlClassLoader.getURLs()) {
            try {
                if (url.toURI().getScheme().equals("file")) {
                    File file = new File(url.toURI());
                    if (file.exists()) {
                        try {
                            if (file.isDirectory()) {
                                for (File listFile : FileUtils.listFiles(file, new String[]{"class"}, true)) {
                                    String classFile = listFile.getAbsolutePath().replace(file.getAbsolutePath(), "").replace(".class", "");
                                    if (classFile.startsWith(File.separator)) {
                                        classFile = classFile.substring(1);
                                    }
                                    classes.add(classFile.replace(File.separator, "."));
                                }
                            } else {
                                JarFile jarFile = new JarFile(file);
                                if (url.getFile().endsWith(".jar")) {
                                    Enumeration<JarEntry> entries = jarFile.entries();
                                    while (entries.hasMoreElements()) {
                                        JarEntry jarEntry = entries.nextElement();
                                        if (jarEntry.getName().endsWith(".class")) {
                                            classes.add(jarEntry.getName().replace(".class", "").replace("/", "."));
                                        }
                                    }
                                }
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            } catch (URISyntaxException e) {
                e.printStackTrace();
            }
        }
        return classes;
    }

введите здесь описание ссылки Service Manager в java получит все классы реализации для интерфейса в J

Другие вопросы по тегам