Je veux faire quelque chose comme ça:
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() );
Je souhaite donc examiner toutes les classes de l'univers de mon application et, lorsque j'en trouve une qui provient d'Animal, je souhaite créer un nouvel objet de ce type et l'ajouter à la liste. Cela me permet d’ajouter des fonctionnalités sans avoir à mettre à jour une liste de choses. Je peux éviter ce qui suit:
List<Animal> animals = new ArrayList<Animal>();
animals.add( new Dog() );
animals.add( new Cat() );
animals.add( new Donkey() );
...
Avec l'approche ci-dessus, je peux simplement créer une nouvelle classe qui étend Animal et elle sera automatiquement récupérée.
MISE À JOUR: 10/16/2008 9:00 heure du Pacifique:
Cette question a généré beaucoup de bonnes réponses - merci. D'après les réponses et mes recherches, j'ai constaté que ce que je veux vraiment faire n'est tout simplement pas possible sous Java. Certaines approches, telles que le mécanisme ServiceLoader de ddimitrov, peuvent fonctionner - mais elles sont très lourdes pour ce que je veux, et je pense que je déplace simplement le problème du code Java dans un fichier de configuration externe. Mise à jour du 5/10/19 (11 ans plus tard!) Il existe maintenant plusieurs bibliothèques qui peuvent aider à cela, selon @ IvanNik's réponseorg.reflections a l'air bien. De plus, ClassGraph de @Luke Hutchison réponse semble intéressant. Il existe également plusieurs autres possibilités dans les réponses.
Une autre façon d’énoncer ce que je veux: une fonction statique dans ma classe Animal recherche et instancie toutes les classes qui héritent de Animal - sans autre configuration/codage. Si je dois configurer, je pourrais aussi bien les instancier dans la classe Animal de toute façon. Je comprends cela parce qu’un programme Java est simplement une fédération lâche de fichiers .class qui est comme ça.
Fait intéressant, il semble que ce soit assez trivial en C #.
J'utilise org.reflections :
Reflections reflections = new Reflections("com.mycompany");
Set<Class<? extends MyInterface>> classes = reflections.getSubTypesOf(MyInterface.class);
Un autre exemple:
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());
}
}
}
Le moyen Java) de faire ce que vous voulez est d'utiliser le mécanisme ServiceLoader .
De plus, de nombreuses personnes utilisent leur fichier dans un emplacement bien connu (par exemple, /META-INF/services/myplugin.properties), puis à l'aide de ClassLoader.getResources () pour énumérer tous les fichiers contenant ce nom de tous les pots. Cela permet à chaque fichier java d’exporter ses propres fournisseurs et vous pouvez les instancier par réflexion en utilisant Class.forName ().
Pensez à cela d'un point de vue orienté aspect. ce que vous voulez faire, en réalité, c’est connaître toutes les classes au moment de l’exécution qui ont étendu la classe Animal. (Je pense que c'est une description légèrement plus précise de votre problème que votre titre; sinon, je ne pense pas que vous ayez une question d'exécution.)
Donc, ce que je pense que vous voulez, c'est créer un constructeur de votre classe de base (Animal) qui ajoute à votre tableau statique (je préfère ArrayLists, moi-même, mais à chacun leur propre) le type de la classe actuelle instanciée.
Donc, à peu près;
public abstract class Animal
{
private static ArrayList<Class> instantiatedDerivedTypes;
public Animal() {
Class derivedClass = this.getClass();
if (!instantiatedDerivedClass.contains(derivedClass)) {
instantiatedDerivedClass.Add(derivedClass);
}
}
Bien sûr, vous aurez besoin d'un constructeur statique sur Animal pour initialiser instiatedDerivedClass ... Je pense que cela fera ce que vous voulez probablement. Notez que cela dépend du chemin d’exécution; si vous avez une classe Dog dérivée d'Animal qui n'est jamais invoquée, vous ne l'aurez pas dans votre liste de classes d'Animaux.
Malheureusement, cela n’est pas tout à fait possible car ClassLoader ne vous indiquera pas quelles classes sont disponibles. Cependant, vous pouvez vous approcher assez près pour faire quelque chose comme ceci:
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 est correct (dans les commentaires) que cela ne fonctionne que pour les applications autonomes Java) et ne fonctionnera pas sous un serveur d'applications.
Vous pouvez utiliser ResolverUtil ( source brute ) à partir de Stripes Framework
si vous avez besoin de quelque chose de simple et rapide sans refactoriser le code existant.
Voici un exemple simple n'ayant chargé aucune des 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 {}
Cela fonctionne aussi dans un serveur d'applications car c'est là que ça a été conçu pour fonctionner;)
Le code fait essentiellement ce qui suit:
ClassLoader#loadClass(String fullyQualifiedName)
Animal.class.isAssignableFrom(loadedClass);
Essayez ClassGraph . (Avertissement, je suis l'auteur.) Pour l'exemple donné, le code ClassGraph serait:
List<Class<Animal>> animals =
new ClassGraph()
.whitelistPackages("com.Zoo.animals")
.enableAllInfo()
.scan()
.getSubclasses(Animal.class.getName())
.loadClasses(Animal.class);
ClassGraph peut faire beaucoup plus que cela aussi - consultez l'API .
Java charge les classes de manière dynamique, afin que votre univers de classes ne soit composé que de celles qui ont déjà été chargées (et non encore déchargées). Peut-être que vous pouvez faire quelque chose avec un chargeur de classes personnalisé qui pourrait vérifier les supertypes de chaque classe chargée. Je ne pense pas qu'il existe une API pour interroger l'ensemble des classes chargées.
utilisez ceci
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);
Modifier:
Merci à tous ceux qui ont répondu à cette question.
Il semble que ce soit effectivement un problème difficile à résoudre. J'ai fini par abandonner et créer un tableau statique et un getter dans ma classe de 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;
}
}
Il semble que Java n’est tout simplement pas configuré pour la découverte de soi comme C #. Je suppose que le problème est que, étant donné que Java app est juste un collection de fichiers .class dans un répertoire/fichier jar quelque part, le moteur d’exécution n’a pas connaissance d’une classe jusqu’à ce qu’elle soit référencée. ce qui n'est pas possible sans sortir du système de fichiers et regarder.
J'aime toujours le code qui peut se découvrir au lieu de devoir le raconter, mais hélas, cela fonctionne aussi.
Merci encore!
En utilisant OpenPojo , vous pouvez effectuer les opérations suivantes:
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));
}
Une solution consiste à faire en sorte que les classes utilisent des initialiseurs statiques ... Je ne pense pas qu'ils soient hérités (cela ne fonctionnera pas s'ils le sont):
public class Dog extends Animal{
static
{
Animal a = new Dog();
//add a to the List
}
Vous devez ajouter ce code à toutes les classes impliquées. Mais cela évite d'avoir une grosse boucle laide quelque part, de tester chaque classe à la recherche d'enfants d'Animal.
Il s’agit d’un problème complexe et vous devrez trouver ces informations à l’aide d’une analyse statique, ce qui n’est pas facilement disponible au moment de l’exécution. Obtenez en gros le chemin de classe de votre application, parcourez les classes disponibles et lisez les informations de bytecode d'une classe dont elle hérite. Notez qu'une classe Dog ne peut pas hériter directement d'Animal, mais peut hériter de Pet qui hérite à son tour d'Animal. Vous devez donc suivre cette hiérarchie.
J'ai résolu ce problème assez élégamment en utilisant des annotations de niveau paquet, puis en faisant en sorte que cette annotation ait pour argument une liste de classes.
Trouver Java implémentant une interface
Les implémentations doivent simplement créer un package-info.Java et insérer l'annotation magique dans la liste des classes qu'elles souhaitent prendre en charge.