Во время выполнения найдите все classы в приложении Java, которые расширяют базовый class

Я хочу сделать что-то вроде этого:

List animals = new ArrayList(); for( Class c: list_of_all_classes_available_to_my_app() ) if (c is Animal) animals.add( new c() ); 

Итак, я хочу посмотреть все classы в юниверсе моего приложения, и когда я нахожу тот, который спускается с Animal, я хочу создать новый объект этого типа и добавить его в список. Это позволяет мне добавлять функциональные возможности, не обновляя список вещей. Я могу избежать следующего:

 List animals = new ArrayList(); animals.add( new Dog() ); animals.add( new Cat() ); animals.add( new Donkey() ); ... 

С приведенным выше подходом я могу просто создать новый class, который расширяет Animal, и он будет автоматически загружаться.

ОБНОВЛЕНИЕ: 10/16/2008 9:00 утра Стандартное тихоокеанское время:

Этот вопрос вызвал массу замечательных отзывов – спасибо. Из ответов и моих исследований я обнаружил, что то, что я действительно хочу сделать, просто невозможно в Java. Существуют подходы, такие как механизм ServiceLoader ddimitrov, который может работать, но они очень тяжелые для того, что я хочу, и я считаю, что просто перетащил проблему из кода Java во внешний файл конфигурации.

Другой способ указать, что я хочу: статическая функция в моем classе Animal находит и создает экземпляры всех classов, которые наследуются от Animal, без дальнейшей настройки / кодирования. Если мне нужно настроить, я мог бы просто создать их экземпляр в classе Animal. Я понимаю, потому что Java-программа – это просто свободная федерация .class-файлов, которая так и есть.

Интересно, что в C # это довольно тривиально .

Я использую org.reflections :

 Reflections reflections = new Reflections("com.mycompany"); Set> classes = reflections.getSubTypesOf(MyInterface.class); 

Java-способ сделать то, что вы хотите, – использовать механизм ServiceLoader .

Также многие люди сворачивают свои собственные, имея файл в известном местоположении пути к методу (например, /META-INF/services/myplugin.properties), а затем используя ClassLoader.getResources (), чтобы перечислять все файлы с этим именем из всех банок. Это позволяет каждой банке экспортировать своих собственных поставщиков, и вы можете создавать их путем отражения с помощью Class.forName ()

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

Так что я думаю, что вы хотите создать конструктор вашего базового classа (Animal), который добавляет к вашему статическому массиву (я предпочитаю, например, ArrayLists, но каждому из них) тип текущего classа, который создается.

Итак, грубо говоря;

 public abstract class Animal { private static ArrayList instantiatedDerivedTypes; public Animal() { Class derivedClass = this.getClass(); if (!instantiatedDerivedClass.contains(derivedClass)) { instantiatedDerivedClass.Add(derivedClass); } } 

Конечно, вам понадобится статический конструктор для Animal для инициализации экземпляра instanceedDerivedClass … Я думаю, что это сделает то, что вы, вероятно, захотите. Обратите внимание, что это зависит от выполнения; если у вас есть class Dog, который происходит от Animal, который никогда не будет вызван, вы не будете иметь его в списке вашего classа животных.

К сожалению, это не совсем возможно, так как ClassLoader не скажет вам, какие classы доступны. Вы можете, однако, довольно близко сделать что-то вроде этого:

 for (String classpathEntry : System.getProperty("java.class.path").split(System.getProperty("path.separator"))) { if (classpathEntry.endsWith(".jar")) { File jar = new File(classpathEntry); JarInputStream is = new JarInputStream(new FileInputStream(jar)); JarEntry entry; while( (entry = is.getNextJarEntry()) != null) { if(entry.getName().endsWith(".class")) { // Class.forName(entry.getName()) and check // for implementation of the interface } } } } 

Изменить: johnstok является правильным (в комментариях), что это работает только для автономных приложений Java и не будет работать под сервером приложений.

Вы можете использовать ResolverUtil ( исходный источник ) из Stripes Framework
если вам нужно что-то простое и быстрое, не рефакторинг существующего кода.

Вот простой пример, не загрузивший ни один из classов:

 package test; import java.util.Set; import net.sourceforge.stripes.util.ResolverUtil; public class BaseClassTest { public static void main(String[] args) throws Exception { ResolverUtil resolver = new ResolverUtil(); resolver.findImplementations(Animal.class, "test"); Set> classes = resolver.getClasses(); for (Class clazz : classes) { System.out.println(clazz); } } } class Animal {} class Dog extends Animal {} class Cat extends Animal {} class Donkey extends Animal {} 

Это также работает на сервере приложений, так как именно там он был разработан для работы;)

Код в основном делает следующее:

  • итерации по всем ресурсам пакета (ов), который вы указываете
  • сохранить только ресурсы, заканчивающиеся на .class
  • Загрузите эти classы с помощью ClassLoader#loadClass(String fullyQualifiedName)
  • Проверяет, является ли Animal.class.isAssignableFrom(loadedClass);

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

Спасибо всем, кто ответил на этот вопрос.

Кажется, это действительно крепкий орешек. Я закончил тем, что отказался от создания статического массива и getter в моем базовом classе.

 public abstract class Animal{ private static Animal[] animals= null; public static Animal[] getAnimals(){ if (animals==null){ animals = new Animal[]{ new Dog(), new Cat(), new Lion() }; } return animals; } } 

Кажется, что Java просто не настроен для самонастраиваемости, как это делает C #. Я полагаю, проблема заключается в том, что, поскольку приложение Java – это всего лишь коллекция файлов .class в файле directory / jar где-то, среда выполнения не знает о classе, пока не будет упомянута ссылка. В это время загрузчик загружает его – то, что я пытаюсь сделать, это обнаружить его, прежде чем ссылаться на него, что невозможно, не выходя из файловой системы и не глядя.

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

Еще раз спасибо!

Используя OpenPojo, вы можете сделать следующее:

 String package = "com.mycompany"; List animals = new ArrayList(); for(PojoClass pojoClass : PojoClassFactory.enumerateClassesByExtendingType(package, Animal.class, null) { animals.add((Animal) InstanceFactory.getInstance(pojoClass)); } 

использовать это

 public static Set getExtendedClasses(Class superClass) { try { ResolverUtil resolver = new ResolverUtil(); resolver.findImplementations(superClass, superClass.getPackage().getName()); return resolver.getClasses(); } catch(Exception e) {Log.d("Log:", " Err: getExtendedClasses() ");} return null; } getExtendedClasses(Animals.class); 

Редактировать:

  • библиотека для (ResolverUtil): полоса

Попробуйте ClassGraph . (Отказ от ответственности, я автор). Для данного примера код ClassGraph будет выглядеть следующим образом:

 List animals = new ClassGraph() .whitelistPackages("com.zoo.animals") .enableAllInfo() .scan() .getSubclasses(Animal.class.getName()) .loadClasses(Animal.class); 

ClassGraph может сделать намного больше, чем это – проверить API .

Это сложная проблема, и вам нужно будет узнать эту информацию, используя статический анализ, который недоступен во время выполнения. В основном получите путь к classу вашего приложения и просмотрите доступные classы и прочитайте информацию об байт-коде classа, на котором он наследует. Обратите внимание, что class Dog не может непосредственно наследовать от Animal, но может наследовать от Pet, который наследуется от Animal, поэтому вам нужно будет отслеживать эту иерархию.

Один из способов – заставить classы использовать статические инициализаторы … Я не думаю, что они наследуются (они не будут работать, если они есть):

 public class Dog extends Animal{ static { Animal a = new Dog(); //add a to the List } 

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

Я решил эту проблему довольно элегантно с помощью аннотаций уровня пакета, а затем сделав эту аннотацию в качестве аргумента списком classов.

Найти classы Java, реализующие интерфейс

Реализации просто необходимо создать package-info.java и включить магическую аннотацию в список classов, которые они хотят поддерживать.

  • Могу ли я получить имена / значения параметров процедурно из текущей исполняемой функции?
  • GetEntryAssembly для веб-приложений
  • Библиотеки атрибутов и отражений для C ++?
  • Как я могу оценивать код C # динамически?
  • Рекурсивно Получить свойства и дочерние свойства classа
  • Тестирование, если объект имеет общий тип в C #
  • Печать отладочной информации об ошибках с помощью java 8 lambda-выражений
  • System.out объявляется как статический окончательный и инициализируется нулем?
  • Как проверить, имеет ли объект определенный метод / свойство?
  • Java: доступ к частному конструктору с параметрами типа
  • Как работать с varargs и reflection
  • Interesting Posts
    Давайте будем гением компьютера.