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

Je veux faire quelque chose comme ceci:

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

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 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 ramassée automatiquement.

Mise à jour: 16/10/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 trouvé que ce que je veux vraiment faire n'est tout simplement pas possible sous Java. Il existe des approches, telles que le mécanisme ServiceLoader de Ddimitrov, qui peuvent fonctionner-mais elles sont très lourdes pour ce que je veux, et je crois que je déplace simplement le problème du code Java vers un fichier de configuration externe.

Une autre façon d'indiquer ce que je veux: une fonction statique dans ma classe Animal trouve et instancie toutes les classes qui héritent D'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 juste une fédération lâche de .fichiers de classe que c'est juste comme ça.

Fait intéressant, il semble que c'est assez trivial en C#.

125
demandé sur sschuberth 2008-10-15 21:07:27

13 réponses

J'utilise org.réflexions:

Reflections reflections = new Reflections("com.mycompany");    
Set<Class<? extends MyInterface>> classes = reflections.getSubTypesOf(MyInterface.class);
131
répondu IvanNik 2015-10-23 14:35:15

La façon Java de faire ce que vous voulez est d'utiliser le mécanismeServiceLoader .

Aussi beaucoup de gens roulent leur propre en ayant un fichier dans un emplacement classpath bien connu (c'est-à-dire/META-INF/services / myplugin.Propriétés), puis en utilisant ClassLoader.getResources() pour énumérer tous les fichiers portant ce nom de tous les pots. Cela permet à chaque jar d'exporter ses propres fournisseurs et vous pouvez les instancier par réflexion en utilisant la classe.forName ()

32
répondu ddimitrov 2016-02-08 23:44:56

Pensez à cela d'un point de vue orienté vers les aspects; ce que vous voulez faire, vraiment, 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 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 leur) le type de la Classe actuelle qui est 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 instantiatedDerivedClass... 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 qui dérive D'Animal qui n'est jamais invoquée, vous ne l'aurez pas dans votre liste de classes D'Animaux.

8
répondu Paul Sonier 2008-10-15 21:07:09

Malheureusement, ce n'est pas tout à fait possible car le ClassLoader ne vous dira pas quelles classes sont disponibles. Vous pouvez, cependant, être assez proche de 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 Java autonomes et ne fonctionnera pas sous un serveur d'applications.

6
répondu jonathan-stafford 2014-12-21 22:02:27

Vous pouvez utiliser ResolverUtil (source brut) de la Rayures Cadre
si vous avez besoin de quelque chose de simple et rapide sans refactoring de 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 également dans un serveur d'applications car c'est là qu'il a été conçu pour fonctionner;)

Le code fait essentiellement ce qui suit:

  • itérer sur toutes les ressources du(DES) paquet (s) vous spécifiez
  • ne conservez que les ressources se terminant par .classe
  • charge ces classes en utilisant ClassLoader#loadClass(String fullyQualifiedName)
  • Vérifie si Animal.class.isAssignableFrom(loadedClass);
4
répondu DJDaveMark 2013-11-21 12:07:50

Java charge dynamiquement les classes, donc votre univers de classes ne serait que ceux qui ont déjà été chargés (et pas encore déchargés). Peut-être que vous pouvez faire quelque chose avec un chargeur de classe personnalisé qui pourrait vérifier les supertypes de chaque classe chargée. Je ne pense pas qu'il y ait une API pour interroger l'ensemble des classes chargées.

2
répondu thoroughly 2008-10-15 17:55:20

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

Il semble que ce soit en effet un écrou difficile à craquer. J'ai fini par abandonner et créer un tableau statique et 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 l'auto-découvrabilité comme C# est. Je suppose que le problème est que depuis une application Java est juste une collection de .fichiers de classe dans un fichier répertoire / jar quelque part, le runtime ne connaît pas une classe jusqu'à ce qu'elle soit référencée. À ce moment là le chargeur se charge ce que j'essaie de faire, c'est de le découvrir avant de le référencer, ce qui n'est pas possible sans aller dans le 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!

1
répondu JohnnyLambada 2008-10-15 19:04:01

En utilisant OpenPojo vous pouvez faire ce qui suit:

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
répondu Osman Shoukry 2015-02-10 05:23:24

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:

  • bibliothèque pour (ResolverUtil) : Bandes
1
répondu Ali Bagheri 2018-05-03 06:15:42

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

List<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 .

1
répondu Luke Hutchison 2018-08-01 03:51:24

C'est un problème difficile et vous devrez trouver cette information en utilisant l'analyse statique, ce n'est pas facilement disponible au moment de l'exécution. Fondamentalement, obtenez le classpath de votre application et parcourez les classes disponibles et lisez les informations de bytecode d'une classe dont elle hérite. Notez qu'un chien de classe peut ne pas hériter directement de L'Animal, mais peut hériter de L'animal qui est tour hérite de L'Animal, de sorte que vous devrez garder une trace de cette hiérarchie.

0
répondu pdeva 2008-10-15 18:52:00

Une façon est de faire en sorte que les classes utilisent un initialiseur statique... Je ne pense pas que ceux-ci 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
}

Il vous faut ajouter ce code à toutes les classes impliquées. Mais cela évite d'avoir une grosse boucle laide quelque part, testant chaque classe à la recherche d'enfants D'Animaux.

0
répondu 2008-10-15 19:29:37

J'ai résolu ce problème assez élégamment en utilisant des Annotations au niveau du paquet et en faisant en sorte que cette annotation ait comme argument une liste de classes.

Trouver des classes Java implémentant une interface

Les implémentations doivent simplement créer un package-info.java et mettre l'annotation magique avec la liste des classes qu'ils veulent soutenir.

0
répondu Adam Gent 2017-05-23 12:26:24