Em tempo de execução, encontrar todas as classes em uma aplicação Java que estendem a classe base

StackOverflow https://stackoverflow.com/questions/205573

  •  03-07-2019
  •  | 
  •  

Pergunta

Eu quero fazer algo parecido com isto:

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

Então, eu quero olhar para todas as classes no universo do meu aplicativo, e quando eu encontrar um que desce do animal, eu quero criar um novo objeto desse tipo e adicioná-lo à lista. Isso me permite adicionar funcionalidade sem ter que atualizar uma lista de coisas. Posso evitar o seguinte:

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

Com a abordagem acima, eu posso simplesmente criar uma nova classe que estende animal e ele vai ter pego automaticamente.

UPDATE: 10/16/2008 09:00 Horário padrão do Pacífico:

Esta questão tem gerado uma série de grandes respostas - obrigado. A partir das respostas e minha pesquisa, descobri que o que eu realmente quero fazer não é apenas possível sob Java. Existem abordagens, tais como mecanismo de ServiceLoader de ddimitrov que podem trabalhar - mas eles são muito pesados ??para o que eu quero, e eu acredito que eu simplesmente mover o problema a partir do código Java para um arquivo de configuração externa Update 5. / 10/19 (11 anos depois!) Existem hoje várias bibliotecas que podem ajudar com isso de acordo com @ resposta de IvanNik org.reflections parece ser bom. Também ClassGraph de @Luke Hutchison resposta parece interessante. Existem várias possibilidades mais nas respostas também.

Outra forma de estado o que eu quero: uma função estática em meus achados classe animal e instancia todas as classes que herdam animal - sem qualquer configuração adicional / codificação. Se eu tiver que configure, assim como eu poderia apenas instanciar-los na classe Animal de qualquer maneira. Eu entendo que porque um programa Java é apenas uma federação frouxa de arquivos .class que isso é apenas a maneira que é.

Curiosamente, parece que este é bastante trivial em C #.

Foi útil?

Solução

Eu uso org.reflections :

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

Outro exemplo:

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

Outras dicas

A maneira Java para fazer o que você quer é usar o mecanismo ServiceLoader .

Além disso, muitas pessoas rolar sua própria por ter um arquivo em um local classpath bem conhecido (ou seja /META-INF/services/myplugin.properties) e, em seguida, usando ClassLoader.getResources () para enumerar todos os arquivos com este nome de todos os frascos . Isso permite que cada frasco de exportar os seus próprios provedores e você pode instanciar-los pela reflexão usando Class.forName ()

Pense sobre isso de um ponto de vista orientado a aspectos; o que você quer fazer, realmente, é conhecer todas as classes em tempo de execução que estenderam a classe Animal. (Eu acho que é uma descrição um pouco mais preciso do seu problema que o seu título, caso contrário, eu não acho que você tem uma pergunta de tempo de execução.)

Então, o que eu acho que você quer é criar um construtor de sua classe base (Animal) que acrescenta à sua matriz estática (eu prefiro ArrayLists, eu mesmo, mas a cada um o seu próprio) o tipo de corrente classe que está sendo instanciado .

Assim, aproximadamente;

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

Claro, você vai precisar de um construtor estático no animal para inicializar instantiatedDerivedClass ... Eu acho que isso vai fazer o que você provavelmente quer. Note-se que esta é a execução caminho-dependente; se você tiver uma classe Dog que deriva de animal que nunca é chamado, você não vai tê-lo em sua lista de classe Animal.

Infelizmente, este não é inteiramente possível que o ClassLoader não vai dizer o que estão disponíveis classes. Você pode, no entanto, ficar bastante perto fazendo algo parecido com isto:

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
            }
        }
    }
}

Editar: johnstok está correta (nos comentários) que isso só funciona para aplicativos Java independentes, e não vai funcionar sob um servidor de aplicativos

.

Você pode usar ResolverUtil ( matéria-fonte ) do Stripes Framework
se você precisar de algo simples e rápido, sem refatoração qualquer código existente.

Aqui está um exemplo simples de não ter carregado qualquer uma das classes:

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 {}

Isso também funciona em um servidor de aplicativos, bem já que é onde ele foi projetado para trabalhar;)

O código faz basicamente o seguinte:

  • iterar sobre todos os recursos no pacote (s) que você especificar
  • manter apenas os recursos que terminam em .class
  • Coloque essas classes usando ClassLoader#loadClass(String fullyQualifiedName)
  • Verifica se Animal.class.isAssignableFrom(loadedClass);

Tente ClassGraph . (Disclaimer: Eu sou o autor). Para o exemplo dado, o código ClassGraph seria:

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

ClassGraph pode fazer muito mais do que isso também - consulte a API .

Java carrega dinamicamente as classes, para que o seu universo das classes seria apenas aqueles que já foram carregados (e ainda não descarregada). Talvez você possa fazer algo com um carregador de classe personalizada que pudesse verificar os supertipos de cada classe carregada. Eu não acho que há uma API para consultar o conjunto de classes carregadas.

Use esta

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

Editar:

Obrigado a todos que responderam a esta pergunta.

Parece que este é realmente um osso duro de roer. Acabei desistindo e criando uma matriz estática e getter em meus baseclass.

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

Parece que Java simplesmente não está configurado para auto-descoberta do caminho C # é. Acho que o problema é que uma vez que um aplicativo Java é apenas uma coleção de arquivos .class em um arquivo em algum lugar diretório / jar, o tempo de execução não sabe sobre uma classe até que ele é referenciado. Naquele tempo as cargas loader-lo -. O que estou tentando fazer é descobri-lo antes de eu fazer referência a ela que não é possível sem sair para o sistema de arquivos e olhando

Eu gosto sempre de código que pode descobrir em si, em vez de me ter que dizer isso sobre si mesmo, mas infelizmente isso funciona também.

Mais uma vez obrigado!

OpenPojo você pode fazer o seguinte:

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

Este é um problema difícil e você precisa descobrir essas informações usando análise estática, não é facilmente disponível em tempo de execução. Basicamente obter o classpath da sua aplicação e verificação através das classes disponíveis e ler as informações bytecode de uma classe que classe que herda. Note-se que uma classe Dog pode não diretamente herdar de animal, mas pode herdar de animal de estimação que é por sua vez herda de Animal, então você vai precisar para manter o controle de que a hierarquia.

Uma maneira é fazer com que as classes usar um inicializadores estáticos ... Eu não acho que estes são herdadas (não vai funcionar se eles são):

public class Dog extends Animal{

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

Ela exige que você adicione este código para todas as classes envolvidas. Mas evita ter um grande lugar laço feio, testando cada pesquisa classe para crianças de Animal.

Eu resolvi este problema muito elegantemente usando nível pacote anotações e, em seguida, fazer essa anotação tem como argumento uma lista de classes.

Localizar classes Java implementação de uma interface

Implementações apenas tem que criar um package-info.java e colocar a anotação magia com a lista de classes que eles querem apoio.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top