Как найти все подклассы данного класса в Java?
Как можно обойти и попытаться найти все подклассы данного класса (или всех реализаторов данного интерфейса) в Java?
На данный момент у меня есть способ сделать это, но я нахожу его довольно неэффективным (мягко говоря).
Метод таков:
- получить список всех имен классов, которые существуют на пути к классу
- загрузите каждый класс и проверьте, является ли он подклассом или реализатором нужного класса или интерфейса
в Eclipse, есть хороший функция называется иерархией типов, которая позволяет показать это довольно эффективно.
Как это сделать и сделать это программно?
15 ответов:
нет другого способа сделать это, кроме того, что вы описали. Подумайте об этом-как кто-нибудь может знать, какие классы расширяют ClassX без сканирования каждого класса на пути к классам?
Eclipse может рассказать вам только о супер-и подклассах в том, что кажется "эффективным" количеством времени, потому что у него уже есть все данные типа, загруженные в точке, где вы нажимаете кнопку "отображать в иерархии типов" (поскольку он постоянно компилирует ваши классы, знает обо всем путь к классам и т. д.).
сканирование для классов не так просто с чистой Java.
spring framework предлагает класс под названием ClassPathScanningCandidateComponentprovider это может сделать то, что вам нужно. В следующем примере будут найдены все подклассы MyClass в организации пакета.образец.пакет
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.
существует проект, который выполняет необходимое сканирование и индексирование вашего пути к классам, чтобы вы могли получить доступ к этой информации...
размышления
анализ метаданных среды выполнения Java, в духе Scannotations
Reflections сканирует ваш путь к классам, индексирует метаданные, позволяет запрашивать их во время выполнения и может сохранять и соберите эту информацию для многих модулей в рамках вашего проекта.Используя отражения, вы можете запросить метаданные для:
- вам все подтипы некоторого типа
- получить все типы аннотаций с некоторыми аннотациями
- получить все типы аннотаций с некоторыми аннотациями, в том числе параметры аннотации соответствия
- получить все методы аннотированы с некоторыми
(отказ от ответственности: я не использовал его, но проекта описание, кажется, точно подходит для ваших нужд.)
Не забывайте, что созданный документация для класса будет включен список известных подклассов (а для интерфейсов-известные реализующие классы).
Я сделал это несколько лет назад. Самый надежный способ сделать это (т. е. с официальными 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; } } }для простоты я удалил синтаксический анализ аргументов командной строки, и я пишу в систему.из а не файл.
Я знаю, что я на несколько лет опоздал на эту вечеринку, но я столкнулся с этим вопросом, пытаясь решить ту же проблему. Вы можете использовать внутренний поиск 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(); } }первая проблема, которую я вижу до сих пор, заключается в том, что я ловлю только классы, которые непосредственно реализуют интерфейс, а не все их подклассы - но небольшая рекурсия никогда никому не повредит.
имея в виду ограничения, упомянутые в других ответах, вы также можете использовать openpojo это
PojoClassFactory( доступно на Maven) следующим образом:for(PojoClass pojoClass : PojoClassFactory.enumerateClassesByExtendingType(packageRoot, Superclass.class, null)) { System.out.println(pojoClass.getClazz()); }здесь
packageRootкорень строку для поиска (например,"com.mycompany"или даже просто"com"), иSuperclassэто ваш супертип (это работает и на интерфейсах).
следует также отметить, что это, конечно, найдет только все те подклассы, которые существуют на вашем текущем пути к классам. Предположительно, это нормально для того, что вы сейчас смотрите, и, скорее всего, вы действительно рассматривали это, но если вы в какой-то момент выпустили не -
finalclass into the wild (для разных уровней "wild") тогда вполне возможно, что кто-то другой написал свой собственный подкласс, о котором вы не будете знать.таким образом, если вы были желая смотрите все подклассы, потому что вы хотите внести изменения и собираетесь увидеть, как это влияет на поведение подклассов - тогда имейте в виду подклассы, которые вы не можете видеть. В идеале все ваши непубличные методы и сам класс должны быть хорошо документированы; внесите изменения в соответствии с этой документацией без изменения семантики методов/непубличных полей, и ваши изменения должны быть обратно совместимы, по крайней мере, для любого подкласса, который следует за вашим определением суперкласса.
причина, по которой вы видите разницу между своей реализацией и Eclipse, заключается в том, что вы сканируете каждый раз, в то время как Eclipse (и другие инструменты) сканируют только один раз (во время загрузки проекта большую часть времени) и создают индекс. В следующий раз, когда вы попросите данные, он не будет сканировать снова, но посмотрите на индекс.
Я просто пишу простое приложение, чтобы использовать
org.reflections.Reflectionsполучить подклассы абстрактного класса:
попробовать ClassGraph. (Отказ от ответственности, я автор). ClassGraph поддерживает сканирование подклассов данного класса, либо во время выполнения, либо во время сборки, но и многое другое. ClassGraph может построить абстрактное представление всего графа класса (все классы, аннотации, методы, параметры метода и поля) в памяти, для всех классов на пути к классам или для классов в пакетах с белым списком, и вы можете запросить этот граф класса, как хотите. Поддержка ClassGraph больше механизмов спецификации classpath и загрузчиков классов чем любой другой сканер, а также легко работает с новой системой модулей JPMS, поэтому, если вы основываете свой код на ClassGraph, ваш код будет максимально переносимым. смотрите API здесь.
Я использую отражение lib, который сканирует ваш classpath для всех подклассов:https://github.com/ronmamo/reflections
вот как это будет сделано:
Reflections reflections = new Reflections("my.project"); Set<Class<? extends SomeType>> subTypes = reflections.getSubTypesOf(SomeType.class);
в зависимости от ваших конкретных требований, в некоторых случаях Java загрузчика услуги механизм может достичь того, что вы ищете.
короче говоря, это позволяет разработчикам явно объявлять, что класс подклассов какой-то другой класс (или реализует некоторый интерфейс), перечисляя его в файле в файле JAR/WAR . Затем он может быть обнаружен с помощью
java.util.ServiceLoaderкласс, который, когда даноClassобъект, будет генерировать экземпляры всех объявленные подклассы этого класса (или, еслиClassпредставляет интерфейс, все классы, реализующие этот интерфейс).основное преимущество этого подхода заключается в том, что нет необходимости вручную сканировать весь путь к классам для подклассов - вся логика обнаружения содержится в
есть недостатки:ServiceLoaderкласс, и он загружает только классы, явно объявленные вMETA-INF/servicesкаталог (не каждый класс на пути к классам).
- не нашел все подклассы, только те, которые явно объявлены. Таким образом, если вам нужно действительно найти все подклассы, этот подход может быть недостаточно.
- это требует от разработчика явного объявления класса под . Это дополнительная нагрузка на разработчика, и может быть подвержен ошибкам.
- The
ServiceLoader.iterator()генерирует экземпляры подкласса, а не ихClassобъекты. Эта причина два вопроса:
- вы не можете сказать, как строятся подклассы - конструктор no-arg используется для создания экземпляров.
- таким образом, подклассы должны иметь конструктор по умолчанию или должны явно объявлять конструктор no-arg.
по-видимому, Java 9 будет решать некоторые из этих недостатков (в частности, те, которые касаются создания экземпляров подклассов).
An Пример
Предположим, вы заинтересованы в поиске классов, реализующих интерфейс
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.
добавьте их на статическую карту внутри (это.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()); } } }
Comments