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() );
Entonces, quiero ver todas las clases en el universo de mi aplicación, y cuando encuentro una que desciende 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 se recogerá automáticamente.
ACTUALIZACIÓN: 16/10/2008 9:00 am Hora estándar del Pacífico:
Esta pregunta ha generado muchas respuestas excelentes, gracias. A partir de las respuestas y mi investigación, descubrí que lo que realmente quiero hacer no es posible con Java. Hay enfoques, como el mecanismo ServiceLoader de ddimitrov que pueden funcionar, 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. Actualización 5/10/19 (¡11 años después!) Ahora hay varias bibliotecas que pueden ayudar con esto según la respuesta de @ IvanNik org.reflections se ve bien. También ClassGraph de la respuesta de @Luke Hutchison parece interesante. Hay varias posibilidades más en las respuestas también.
Otra forma de decir lo que quiero: una función estática en mi clase Animal encuentra e instancia todas las clases que heredan de Animal, sin ninguna configuración / codificación adicional. Si tengo que configurar, igual podría instanciarlos en la clase Animal de todos modos. Entiendo que debido a que un programa Java es solo una federación suelta de archivos .class, así es como es.
Curiosamente, parece que esto es bastante trivial en C #.
fuente
Respuestas:
Yo uso org.reflections :
Otro ejemplo:
fuente
animals.add( new c() );
de su código), porque mientrasClass.newInstance()
exista, no tiene garantía de que la clase específica tenga un constructor sin parámetros (C # le permite requerir eso en un genérico)La forma Java de hacer lo que quiere es usar el mecanismo ServiceLoader .
Además, muchas personas obtienen sus propios archivos al tener un archivo en una ubicación conocida de classpath (es decir, /META-INF/services/myplugin.properties) y luego usan ClassLoader.getResources () para enumerar todos los archivos con este nombre de todos los archivos jar . Esto permite que cada jarra exporte sus propios proveedores y puede instanciarlos mediante reflexión usando Class.forName ()
fuente
Piense en esto desde un punto de vista orientado a aspectos; lo que quieres hacer, realmente, es conocer todas las clases en tiempo de ejecución que HAN extendido la clase Animal. (Creo que 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 en tiempo de ejecución).
Entonces, lo que creo que desea es crear un constructor de su clase base (Animal) que agregue a su matriz estática (prefiero ArrayLists, yo mismo, pero a cada uno lo suyo) el tipo de la Clase actual que se está instanciando.
Entonces, más o menos;
Por supuesto, necesitará un constructor estático en Animal para inicializar instanciadaDerivedClass ... Creo que esto hará lo que probablemente quiera. Tenga en cuenta que esto depende de la ruta de ejecución; Si tiene una clase de Perro que se deriva de Animal que nunca se invoca, no la tendrá en su lista de Clase de Animal.
fuente
Desafortunadamente, esto no es totalmente posible ya que ClassLoader no le dirá qué clases están disponibles. Sin embargo, puede acercarse bastante haciendo algo como esto:
Editar: johnstok es correcto (en los comentarios) que esto solo funciona para aplicaciones Java independientes y no funcionará bajo un servidor de aplicaciones.
fuente
URL[]
pero no siempre, por lo que puede que no sea posible) desde la jerarquía de ClassLoader. A menudo, aunque debe tener permiso, ya que generalmente tendría unaSecurityManager
carga dentro de la JVM.Puede usar ResolverUtil ( fuente sin procesar ) del Stripes Framework
si necesita algo simple y rápido sin refactorizar ningún código existente.
Aquí hay un ejemplo simple que no ha cargado ninguna de las clases:
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:
ClassLoader#loadClass(String fullyQualifiedName)
Animal.class.isAssignableFrom(loadedClass);
fuente
El mecanismo más robusto para enumerar todas las subclases de una clase dada es actualmente ClassGraph , porque maneja la gama más amplia posible de mecanismos de especificación de classpath , incluido el nuevo sistema de módulos JPMS. (Soy el autor)
fuente
Java carga clases dinámicamente, por lo que su universo de clases sería solo aquellas que ya se han cargado (y aún no se han descargado). Quizás 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.
fuente
utilizar este
Editar:
fuente
Gracias a todos los que respondieron esta pregunta.
Parece que esta es una nuez difícil de roer. Terminé rindiéndome y creando una matriz estática y getter en mi clase base.
Parece que Java simplemente no está configurado para la autodescubrimiento como C #. Supongo que el problema es que, dado que una aplicación Java es solo una colección de archivos .class en un archivo directorio / jar en alguna parte, el tiempo de ejecución no conoce una clase hasta que se hace referencia a ella. En ese momento, el cargador lo carga; lo que intento hacer es descubrirlo antes de hacer referencia a él, lo que no es posible sin salir al sistema de archivos y buscarlo.
Siempre me gusta el código que puede descubrirse a sí mismo en lugar de que tenga que contarlo sobre sí mismo, pero desafortunadamente esto también funciona.
¡Gracias de nuevo!
fuente
Usando OpenPojo puedes hacer lo siguiente:
fuente
Este es un problema difícil y necesitará encontrar esta información mediante análisis estático, no está disponible fácilmente en tiempo de ejecución. Básicamente, obtenga el classpath de su aplicación, 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 una clase Dog no puede heredar directamente de Animal, pero puede heredar de Pet, que a su vez hereda de Animal, por lo que deberá realizar un seguimiento de esa jerarquía.
fuente
Una forma es hacer que las clases usen inicializadores estáticos ... No creo que se hereden (no funcionará si lo son):
Requiere que agregue este código a todas las clases involucradas. Pero evita tener un gran círculo feo en alguna parte, probando cada clase buscando niños de Animal.
fuente
Resolví este problema con bastante elegancia usando las anotaciones de nivel de paquete y luego haciendo que esa anotación tenga como argumento una lista de clases.
Encuentra clases de Java implementando una interfaz
Las implementaciones solo tienen que crear un paquete-info.java y poner la anotación mágica con la lista de clases que desean admitir.
fuente