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

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

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

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

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

Ответ 1

Я использую 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());
        }
    }
}

Ответ 2

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

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

Ответ 3

Подумайте об этом с точки зрения аспекта; то, что вы хотите сделать, действительно, знает все классы во время выполнения, которые расширили класс 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 для инициализации экземпляра instanceedDerivedClass... Я думаю, что это сделает то, что вы, вероятно, захотите. Обратите внимание, что это зависит от выполнения; если у вас есть класс Dog, который происходит от Animal, который никогда не будет вызван, вы не будете иметь его в списке классов животных.

Ответ 4

К сожалению, это не совсем возможно, поскольку 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 и не будет работать под сервером приложений.

Ответ 5

Вы можете использовать ResolverUtil (исходный источник) из 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);

Ответ 6

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

List<Class<Animal>> animals;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("com.zoo.animals")
        .enableClassInfo().scan()) {
    animals = scanResult
        .getSubclasses(Animal.class.getName())
        .loadClasses(Animal.class);
}

Ответ 7

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

Ответ 8

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

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

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

Ответ 9

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

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

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

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

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

Ответ 10

Используя 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));
}

Ответ 11

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

Ответ 12

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

public class Dog extends Animal{

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

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

Ответ 13

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

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

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