En tiempo de ejecución, busque todas las clases en una aplicación Java que amplíen una clase base


Quiero hacer algo como esto:

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

Por lo tanto, quiero mirar todas las clases en el universo de mi aplicación, y cuando encuentre una que descienda de Animal, quiero crear un nuevo objeto de ese tipo y agregarlo a la lista. Esto me permite agregar funcionalidad sin tener que actualizar una lista de cosas. Puedo evitar lo siguiente:

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

Con el enfoque anterior, simplemente puedo crear una nueva clase que extienda Animal y será recogida automática.

ACTUALIZACIÓN:16/10/2008 9: 00 a. m. Hora estándar del Pacífico:

Esta pregunta ha generado muchas respuestas geniales thank gracias. A partir de las respuestas y mi investigación, he encontrado que lo que realmente quiero hacer no es posible bajo Java. Hay enfoques, como el mecanismo ServiceLoader de ddimitrov que pueden funcionar but pero son muy pesados para lo que quiero, y creo que simplemente muevo el problema del código Java a un archivo de configuración externo.

Otra forma de decir lo que quiero: una función estática en mi clase Animal encuentra e instanciaas todas las clases que heredan de Animal without sin ninguna configuración/codificación adicional. Si tengo que configurar, también podría instanciarlos en la clase Animal de todos modos. Entiendo que porque un programa Java es solo una federación suelta de .archivos de clase que así son las cosas.

Curiosamente, parece que esto es bastante trivial en C#.

Author: sschuberth, 2008-10-15

13 answers

Utilizo org.reflexiones:

Reflections reflections = new Reflections("com.mycompany");    
Set<Class<? extends MyInterface>> classes = reflections.getSubTypesOf(MyInterface.class);
 131
Author: IvanNik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-23 14:35:15

La forma Java de hacer lo que quieres es usar el mecanismo ServiceLoader.

También muchas personas ruedan su propio archivo al tener un archivo en una ubicación de classpath bien conocida (es decir, /META-INF/services/myplugin.propiedades) y luego usando ClassLoader.getResources () para enumerar todos los archivos con este nombre de todos los jars. Esto permite que cada jar exporte sus propios proveedores y puede instanciarlos mediante reflexión utilizando Class.forName ()

 32
Author: ddimitrov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-08 23:44:56

Piense en esto desde un punto de vista orientado a aspectos; lo que realmente quiere hacer es conocer todas las clases en tiempo de ejecución que han extendido la clase Animal. (Creo que esa es una descripción un poco más precisa de su problema que su título; de lo contrario, no creo que tenga una pregunta de tiempo de ejecución.)

Así que lo que creo que desea es crear un constructor de su clase base (Animal) que se suma a su matriz estática (prefiero ArrayLists, yo mismo, pero a cada uno su propio) el tipo de la clase actual que está siendo instanciada.

Así que, aproximadamente;

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

Por supuesto, necesitarás un constructor estático en Animal para inicializar instantiatedDerivedClass... Creo que esto hará lo que probablemente quieras. Tenga en cuenta que esto depende de la ruta de ejecución; si tiene una clase Dog que deriva de Animal que nunca se invoca, no la tendrá en su lista de clases Animal.

 8
Author: Paul Sonier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-15 21:07:09

Desafortunadamente esto no es del todo posible ya que el ClassLoader no le dirá qué clases están disponibles. Sin embargo, puedes acercarte bastante haciendo algo como esto:

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

Edit: johnstok es correcto (en los comentarios) que esto solo funciona para aplicaciones Java independientes, y no funcionará bajo un servidor de aplicaciones.

 6
Author: jonathan-stafford,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-21 22:02:27

Puedes usar ResolverUtil (fuente cruda ) del Marco de Franjas
si necesita algo simple y rápido sin refactorizar ningún código existente.

Aquí hay un ejemplo simple de no haber cargado ninguna de las clases:

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

Esto también funciona en un servidor de aplicaciones, ya que es donde fue diseñado para funcionar;)

El código básicamente hace lo siguiente:

  • iterar sobre todos los recursos en el paquete(s) usted especifica
  • mantenga solo los recursos que terminan en .clase
  • Cargue esas clases usando ClassLoader#loadClass(String fullyQualifiedName)
  • Comprueba si Animal.class.isAssignableFrom(loadedClass);
 4
Author: DJDaveMark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-21 12:07:50

Java carga clases dinámicamente, por lo que su universo de clases serían solo aquellas que ya se han cargado (y aún no se han descargado). Tal vez pueda hacer algo con un cargador de clases personalizado que pueda verificar los supertipos de cada clase cargada. No creo que haya una API para consultar el conjunto de clases cargadas.

 2
Author: thoroughly,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-15 17:55:20

Gracias a todos los que respondieron a esta pregunta.

Parece que este es un hueso duro de roer. Terminé renunciando y creando una matriz estática y getter en mi clase base.

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 simplemente no está configurado para la auto-descubribilidad de la manera en que C# es. Supongo que el problema es que desde una aplicación Java es solo una colección de .archivos de clase en un archivo de directorio / jar en algún lugar, el tiempo de ejecución no conoce una clase hasta que se hace referencia a ella. En ese momento el cargador carga lo que estoy tratando de hacer es descubrirlo antes de referenciarlo, lo cual no es posible sin ir al sistema de archivos y mirar.

Siempre me gusta el código que puede descubrirse a sí mismo en lugar de tener que contarlo sobre sí mismo, pero por desgracia esto también funciona.

Gracias de nuevo!

 1
Author: JohnnyLambada,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-15 19:04:01

Usando OpenPojo puedes hacer lo siguiente:

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
Author: Osman Shoukry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-10 05:23:24

Usa esto

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:

  • biblioteca para (ResolverUtil): Rayas
 1
Author: Ali Bagheri,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-03 06:15:42

Intenta ClassGraph. (Descargo de responsabilidad, soy el autor). Para el ejemplo dado, el código ClassGraph sería:

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

ClassGraph puede hacer mucho más que esto también {echa un vistazo a la API .

 1
Author: Luke Hutchison,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-01 03:51:24

Este es un problema difícil y tendrá que encontrar esta información utilizando análisis estático, no está disponible fácilmente en tiempo de ejecución. Básicamente, obtenga el classpath de su aplicación y escanee las clases disponibles y lea la información de código de bytes de una clase de la que hereda. Tenga en cuenta que un Perro de clase no puede heredar directamente de Animal,pero podría heredar de Mascota que es a su vez hereda de Animal, por lo que tendrá que realizar un seguimiento de esa jerarquía.

 0
Author: pdeva,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-15 18:52:00

Una forma es hacer que las clases usen inicializadores estáticos... No creo que estos sean heredados (no funcionará si lo son):

public class Dog extends Animal{

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

Requiere que agregue este código a todas las clases involucradas. Pero evita tener un gran bucle feo en algún lugar, probando cada clase en busca de hijos de Animales.

 0
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-15 19:29:37

Resolví este problema con bastante elegancia usando Anotaciones a nivel de paquete y luego haciendo que la anotación tenga como argumento una lista de clases.

Buscar clases Java implementando una interfaz

Las implementaciones solo tienen que crear un paquete-info.java y poner la anotación mágica en la lista de clases que quieren apoyar.

 0
Author: Adam Gent,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:24