À l’exécution, recherchez toutes les classes d’une application Java qui étendent une classe de base

Je veux faire quelque chose comme ça:

List animals = new ArrayList(); for( Class c: list_of_all_classes_available_to_my_app() ) if (c is Animal) animals.add( new c() ); 

Donc, je veux regarder toutes les classes dans l’univers de mon application, et quand j’en trouve une qui descend d’Animal, je veux créer un nouvel object de ce type et l’append à la liste. Cela me permet d’append des fonctionnalités sans avoir à mettre à jour une liste de choses. Je peux éviter ce qui suit:

 List animals = new ArrayList(); 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 prise en compte.

MISE À JOUR: 10/16/2008 9h00 heure normale 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 voulais vraiment faire n’était 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 crois que je ne fais que déplacer le problème du code Java vers un fichier de configuration externe.

Une autre façon de définir ce que je veux: une fonction statique dans ma classe Animal recherche et instancie toutes les classes qui héritent de Animal – sans aucune configuration / codage supplémentaire. Si je dois configurer, je pourrais tout aussi bien les instancier dans la classe Animal. Je comprends cela car un programme Java est juste une fédération de fichiers .class qui est comme ça.

Fait intéressant, il semble que ce soit assez sortingvial en C #.

    J’utilise org.reflections :

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

    Le moyen Java de faire ce que vous voulez est d’utiliser le mécanisme ServiceLoader .

    En outre, de nombreuses personnes ont leur propre fichier en ayant un fichier dans un emplacement de classpath bien connu (par exemple /META-INF/services/myplugin.properties), puis en utilisant ClassLoader.getResources () pour énumérer tous les fichiers portant ce nom dans tous les fichiers JAR . Cela permet à chaque jar d’exporter ses propres fournisseurs et vous pouvez les instancier par reflection en utilisant Class.forName ()

    Pensez à cela d’un sharepoint vue orienté vers l’aspect; Ce que vous voulez vraiment faire, c’est connaître toutes les classes à l’exécution qui ont étendu la classe Animal. (Je pense que c’est une description un peu plus précise de votre problème que de 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) le type de la classe actuelle qui est instanciée.

    Donc, grosso modo

     public abstract class Animal { private static ArrayList 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 instanciéDérivéClasse … 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 de chien dérivée d’un animal qui n’est jamais invoquée, vous ne l’aurez pas dans la liste des classes d’animaux.

    Malheureusement, ce n’est pas tout à fait possible car le ClassLoader ne vous dira pas quelles classes sont disponibles. Vous pouvez, cependant, devenir assez proche en faisant quelque chose comme ceci:

     for (Ssortingng 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 a raison (dans les commentaires) que cela ne fonctionne que pour les applications Java autonomes et ne fonctionnera pas sous un serveur d’applications.

    Vous pouvez utiliser ResolverUtil ( source brute ) du Framework Ssortingpes
    si vous avez besoin de quelque chose de simple et de rapide sans refactorisation de code existant.

    Voici un exemple simple de n’avoir chargé aucune des classes:

     package test; import java.util.Set; import net.sourceforge.ssortingpes.util.ResolverUtil; public class BaseClassTest { public static void main(Ssortingng[] args) throws Exception { ResolverUtil resolver = new ResolverUtil(); resolver.findImplementations(Animal.class, "test"); Set> classes = resolver.getClasses(); for (Class clazz : classes) { System.out.println(clazz); } } } class Animal {} class Dog extends Animal {} class Cat extends Animal {} class Donkey extends Animal {} 

    Cela fonctionne également sur un serveur d’application car c’est là qu’il a été conçu pour fonctionner;)

    Le code fait essentiellement ce qui suit:

    • itérer toutes les ressources du ou des paquets que vous spécifiez
    • ne garder que les ressources se terminant par .class
    • Chargez ces classes à l’aide de ClassLoader#loadClass(Ssortingng fullyQualifiedName)
    • Vérifie si Animal.class.isAssignableFrom(loadedClass);

    Java charge dynamicment les classes, donc votre univers de classes ne serait que celles qui ont déjà été chargées (et pas encore déchargées). Vous pouvez peut-être faire quelque chose avec un chargeur de classe personnalisé qui pourrait vérifier les super-types de chaque classe chargée. Je ne pense pas qu’il existe une API pour interroger l’ensemble des classes chargées.

    Merci à tous ceux qui ont répondu à cette question.

    Il semble bien que ce soit vraiment 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 ne soit pas configuré pour être auto-découvrable comme C # l’est. Je suppose que le problème est que comme une application Java est juste une collection de fichiers .class dans un fichier répertoire / jar quelque part, le runtime ne connaît pas une classe tant qu’elle n’est pas référencée. A ce moment, le chargeur le charge – ce que j’essaie de faire est de le découvrir avant de le référencer, ce qui n’est pas possible sans aller au système de fichiers et regarder.

    J’aime toujours le code qui peut se découvrir au lieu de devoir le dire sur lui-même, mais hélas cela fonctionne aussi.

    Merci encore!

    En utilisant OpenPojo, vous pouvez effectuer les opérations suivantes:

     Ssortingng package = "com.mycompany"; List animals = new ArrayList(); for(PojoClass pojoClass : PojoClassFactory.enumerateClassesByExtendingType(package, Animal.class, null) { animals.add((Animal) InstanceFactory.getInstance(pojoClass)); } 

    utilisez ceci

     public static Set 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:

    • bibliothèque pour (ResolverUtil): Ssortingpes

    Essayez ClassGraph . (Disclaimer, je suis l’auteur). Pour l’exemple donné, le code ClassGraph serait:

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

    ClassGraph peut faire beaucoup plus que cela – consultez l’API .

    C’est un problème difficile et vous devrez trouver cette information en utilisant une parsing statique, elle n’est pas facilement disponible à l’exécution. Fondamentalement, récupérez 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 de Animal mais peut hériter de Pet qui est héritée d’Animal, vous devrez donc garder une trace de cette hiérarchie.

    Une façon consiste à faire en sorte que les classes utilisent des initialiseurs statiques … Je ne pense pas que ces classes soient héritées (cela ne fonctionnera pas si elles le sont):

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

    Vous devez append ce code à toutes les classes impliquées. Mais cela évite d’avoir une grosse boucle laide quelque part, testant chaque classe cherchant des enfants d’Animal.

    J’ai résolu ce problème de manière très élégante en utilisant les annotations au niveau du paquet, puis en faisant de cette annotation une liste de classes.

    Rechercher des classes 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’ils souhaitent prendre en charge.