Во время выполнения найдите все классы в приложении Java, которые расширяют базовый класс - PullRequest
137 голосов
/ 15 октября 2008

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

List<Animal> animals = new ArrayList<Animal>();

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

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

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

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

ОБНОВЛЕНИЕ: 16.10.2008 9:00 по тихоокеанскому стандартному времени:

Этот вопрос вызвал много хороших ответов - спасибо. Из ответов и моих исследований я обнаружил, что то, что я действительно хочу сделать, просто невозможно в Java. Существуют подходы, такие как механизм ServiceLoader ddimitrov, который может работать, но они очень тяжелы для того, что я хочу, и я считаю, что я просто перенесу проблему из кода Java во внешний файл конфигурации. Обновление 5 / 10/19 (11 лет спустя!) В настоящее время есть несколько библиотек, которые могут помочь с этим, согласно @ 1015 * answer org.reflections @ IvanNik *. Также ClassGraph от ответа @Luke Hutchison выглядит интересно. В ответах есть еще несколько возможностей.

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

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

Ответы [ 13 ]

144 голосов
/ 11 февраля 2012

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

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

Другой пример:

public static void main(String[] args) throws IllegalAccessException, InstantiationException {
    Reflections reflections = new Reflections("java.util");
    Set<Class<? extends List>> classes = reflections.getSubTypesOf(java.util.List.class);
    for (Class<? extends List> aClass : classes) {
        System.out.println(aClass.getName());
        if(aClass == ArrayList.class) {
            List list = aClass.newInstance();
            list.add("test");
            System.out.println(list.getClass().getName() + ": " + list.size());
        }
    }
}
33 голосов
/ 16 октября 2008

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

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

8 голосов
/ 16 октября 2008

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

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

Итак, примерно;

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

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

6 голосов
/ 15 октября 2008

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

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 и не будет работать на сервере приложений.

4 голосов
/ 21 ноября 2013

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

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

package test;

import java.util.Set;
import net.sourceforge.stripes.util.ResolverUtil;

public class BaseClassTest {
    public static void main(String[] args) throws Exception {
        ResolverUtil<Animal> resolver = new ResolverUtil<Animal>();
        resolver.findImplementations(Animal.class, "test");
        Set<Class<? extends Animal>> classes = resolver.getClasses();

        for (Class<? extends Animal> clazz : classes) {
            System.out.println(clazz);
        }
    }
}

class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
class Donkey extends Animal {}

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

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

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

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

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

ClassGraph также может сделать намного больше - проверить API .

2 голосов
/ 07 апреля 2018

используйте это

public static Set<Class> 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);

Edit:

2 голосов
/ 15 октября 2008

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

1 голос
/ 10 февраля 2015

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

String package = "com.mycompany";
List<Animal> animals = new ArrayList<Animal>();

for(PojoClass pojoClass : PojoClassFactory.enumerateClassesByExtendingType(package, Animal.class, null) {
  animals.add((Animal) InstanceFactory.getInstance(pojoClass));
}
1 голос
/ 15 октября 2008

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

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

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 в каком-либо каталоге / jar-файле, среда выполнения не знает о классе, пока на него не ссылаются. В это время загрузчик загружает его - я пытаюсь обнаружить его, прежде чем ссылаться на него, что невозможно без выхода в файловую систему и поиска.

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...