Comment trouver toutes les sous-classes D'une classe donnée en Java?

Comment trouver toutes les sous-classes d'une classe donnée (ou tous les implémentations d'une interface donnée) en Java? Maintenant, j'ai une méthode pour faire cela, mais je trouve ça assez inefficace (pour dire le moins). La méthode est la suivante:

  1. Obtenir une liste de tous les noms de classe qui existent sur le chemin de classe
  2. charger chaque classe et effectuer les essais pour voir s'il s'agit d'une sous-classe ou d'un implant de la classe ou de l'interface désirée

dans Eclipse, il y a une belle fonctionnalité appelée la hiérarchie de Type qui parvient à le montrer assez efficacement. Comment s'y prendre et le faire de façon programmatique?

176
demandé sur guerda 2009-01-29 18:53:02

15 réponses

il n'y a pas d'autre moyen de le faire que ce que vous avez décrit. Pensez - y-comment quelqu'un peut-il savoir quelles classes étendent ClassX sans scanner chaque classe sur le chemin de classe?

Eclipse ne peut vous parler que du super et des sous-classes dans ce qui semble être une quantité de temps "efficace" parce qu'il a déjà toutes les données de type chargé à l'endroit où vous appuyez sur le bouton "Affichage dans la hiérarchie de Type" (depuis qu'il est la compilation constante de vos classes, sait sur tout sur le chemin de la classe, etc).

67
répondu matt b 2009-01-29 15:58:52

Scanner pour les classes n'est pas facile avec pur Java.

Le framework spring propose une classe appelée ClassPathScanningCandidateComponentprovider qui peut faire ce dont vous avez besoin. L'exemple suivant pourrait trouver toutes les sous-classes de MyClass dans le paquet org.exemple.paquet

ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
provider.addIncludeFilter(new AssignableTypeFilter(MyClass.class));

// scan in org.example.package
Set<BeanDefinition> components = provider.findCandidateComponents("org/example/package");
for (BeanDefinition component : components)
{
    Class cls = Class.forName(component.getBeanClassName());
    // use class cls found
}

cette méthode présente l'avantage supplémentaire d'utiliser un analyseur de bytecodes pour trouver les candidats, ce qui signifie qu'elle n'est pas charge toutes les classes qu'il scanne.

107
répondu fforw 2017-11-12 16:15:12

ce N'est pas possible d'utiliser uniquement L'API Java Reflections intégrée.

un projet existe qui fait la numérisation et l'indexation nécessaires de votre classpath afin que vous puissiez accéder à cette information...

réflexions

Java runtime analyse des métadonnées, dans l'esprit de Scannotations

Reflections scanne votre chemin de classe, indexe les métadonnées, vous permet de l'interroger sur l'exécution et peut enregistrer et recueillir cette information pour de nombreux modules dans votre projet.

En utilisant Reflections vous pouvez interroger vos métadonnées pour:

  • obtenir tous les sous-types d'un type quelconque
  • obtenir tous les types annotés avec quelques annotations
  • obtenir tous les types annotés avec une annotation, y compris les paramètres d'annotation correspondant
  • obtenir toutes les méthodes annoté avec quelques

(avertissement: je n'ai pas utilisé, mais la description du projet semble être parfaitement adaptées à vos besoins.)

42
répondu Mark Renouf 2009-01-29 16:24:52

n'oubliez pas que le Javadoc généré pour une classe comprendra une liste de sous-classes connues (et pour les interfaces, classes d'implémentation connues).

10
répondu Rob 2009-01-29 22:57:11

j'ai fait ça il y a plusieurs années. La façon la plus fiable de le faire (c.-à-d. avec des API Java officielles et sans dépendances externes) est d'écrire un doclet personnalisé pour produire une liste qui peut être lue à l'exécution.

vous pouvez l'exécuter à partir de la ligne de commande comme ceci:

javadoc -d build -doclet com.example.ObjectListDoclet -sourcepath java/src -subpackages com.example

ou exécuter à partir d'ant comme ceci:

<javadoc sourcepath="${src}" packagenames="*" >
  <doclet name="com.example.ObjectListDoclet" path="${build}"/>
</javadoc>

voici le code de base:

public final class ObjectListDoclet {
    public static final String TOP_CLASS_NAME =  "com.example.MyClass";        

    /** Doclet entry point. */
    public static boolean start(RootDoc root) throws Exception {
        try {
            ClassDoc topClassDoc = root.classNamed(TOP_CLASS_NAME);
            for (ClassDoc classDoc : root.classes()) {
                if (classDoc.subclassOf(topClassDoc)) {
                    System.out.println(classDoc);
                }
            }
            return true;
        }
        catch (Exception ex) {
            ex.printStackTrace();
            return false;
        }
    }
}

pour la simplicité, j'ai supprimé parsing des arguments en ligne de commande et j'écris au système.plutôt que d'un fichier.

8
répondu David Leppik 2012-05-14 21:08:28

je sais que je suis en retard à cette fête, mais je suis tombé sur cette question en essayant de résoudre le même problème. Vous pouvez utiliser la recherche interne d'Eclipse de manière programmatique, si vous écrivez un Plugin Eclipse (et ainsi profiter de leur mise en cache, etc), pour trouver des classes qui implémentent une interface. Voici ma première coupe (très grossière):

  protected void listImplementingClasses( String iface ) throws CoreException
  {
    final IJavaProject project = <get your project here>;
    try
    {
      final IType ifaceType = project.findType( iface );
      final SearchPattern ifacePattern = SearchPattern.createPattern( ifaceType, IJavaSearchConstants.IMPLEMENTORS );
      final IJavaSearchScope scope = SearchEngine.createWorkspaceScope();
      final SearchEngine searchEngine = new SearchEngine();
      final LinkedList<SearchMatch> results = new LinkedList<SearchMatch>();
      searchEngine.search( ifacePattern, 
      new SearchParticipant[]{ SearchEngine.getDefaultSearchParticipant() }, scope, new SearchRequestor() {

        @Override
        public void acceptSearchMatch( SearchMatch match ) throws CoreException
        {
          results.add( match );
        }

      }, new IProgressMonitor() {

        @Override
        public void beginTask( String name, int totalWork )
        {
        }

        @Override
        public void done()
        {
          System.out.println( results );
        }

        @Override
        public void internalWorked( double work )
        {
        }

        @Override
        public boolean isCanceled()
        {
          return false;
        }

        @Override
        public void setCanceled( boolean value )
        {
        }

        @Override
        public void setTaskName( String name )
        {
        }

        @Override
        public void subTask( String name )
        {
        }

        @Override
        public void worked( int work )
        {
        }

      });

    } catch( JavaModelException e )
    {
      e.printStackTrace();
    }
  }

le premier problème que je vois jusqu'à présent est que je ne capture que les classes qui implémentent directement l'interface, pas toutes leurs sous-classes - mais un peu de récursion n'a jamais fait de mal à personne.

7
répondu Curtis 2011-04-20 19:29:56

en gardant à l'esprit les limitations mentionnées dans les autres réponses, vous pouvez également utiliser openpojo's PojoClassFactory ( disponible sur Maven ) de la manière suivante:

for(PojoClass pojoClass : PojoClassFactory.enumerateClassesByExtendingType(packageRoot, Superclass.class, null)) {
    System.out.println(pojoClass.getClazz());
}

packageRoot est la chaîne racine des paquets que vous souhaitez rechercher (par exemple "com.mycompany" ou tout simplement "com" ), et Superclass est votre supertype (cela fonctionne également sur les interfaces).

5
répondu mikołak 2013-08-11 18:34:56

Il convient de noter que bien que cela n'trouver tous les sous-classes qui existent sur votre classpath. Probablement C'est OK pour ce que vous regardez actuellement, et il y a des chances que vous ayez considéré cela, mais si vous avez à un moment donné libéré une classe non- final dans la nature (pour des niveaux variables de "sauvage") alors il est tout à fait possible que quelqu'un d'autre ait écrit leur propre sous-classe que vous ne saurez pas.

ainsi si vous se trouve à vouloir voir toutes les sous-classes parce que vous voulez faire un changement et allez voir comment il affecte le comportement des sous - classes-alors gardez à l'esprit les sous-classes que vous ne pouvez pas voir. Idéalement, toutes vos méthodes non privées, et la classe elle-même devrait être bien documentée; faire des changements selon cette documentation sans changer la sémantique des méthodes/domaines non privés et vos changements devraient être rétrocompatibles, pour toute sous-classe qui a suivi votre définition de la super-classe au moins.

3
répondu Andrzej Doyle 2009-01-29 16:31:31

la raison pour laquelle vous voyez une différence entre votre implémentation et Eclipse est que vous numérisez à chaque fois, tandis Qu'Eclipse (et d'autres outils) numérisent seulement une fois (pendant la charge de projet la plupart du temps) et créent un index. La prochaine fois que vous demandez les données il ne scanne pas à nouveau, mais regardez l'index.

3
répondu OscarRyz 2011-11-03 11:16:16

je viens d'écrire une démo simple pour utiliser le org.reflections.Reflections pour obtenir des sous-classes de classe abstraite:

https://github.com/xmeng1/ReflectionsDemo

3
répondu Xin Meng 2017-02-02 17:32:36

Try ClassGraph . (Avertissement, je suis l'auteur). ClassGraph supporte la numérisation pour les sous-classes d'une classe donnée, soit à l'exécution ou au temps de construction, mais aussi beaucoup plus. ClassGraph peut construire une représentation abstraite de l'ensemble du Graphe de classe (toutes les classes, les annotations, les méthodes, les paramètres de méthode et les champs) en mémoire, pour toutes les classes sur classpath, ou pour les classes dans les paquets de liste blanche, et vous pouvez interroger ce graphe de classe comme vous le souhaitez. ClassGraph prend en charge plus de mécanismes de spécification classpath et classloaders que tout autre scanner, et fonctionne également de manière transparente avec le nouveau système de module JPMS, donc si vous basez votre code sur ClassGraph, votre code sera portable au maximum. voir L'API ici.

3
répondu Luke Hutchison 2018-08-01 04:12:47

j'utilise une couche de réflexion, qui scanne votre classpath pour toutes les sous-classes: https://github.com/ronmamo/reflections

C'est comment il devrait être fait:

Reflections reflections = new Reflections("my.project");
Set<Class<? extends SomeType>> subTypes = reflections.getSubTypesOf(SomeType.class);
2
répondu futchas 2017-08-03 08:48:39

selon vos exigences particulières, dans certains cas, le mécanisme service loader de Java pourrait vous permettre d'atteindre ce que vous recherchez.

en bref, il permet aux développeurs de déclarer explicitement qu'une classe sous-classe une autre classe (ou implémente une interface) en l'inscrivant dans un fichier dans le répertoire META-INF/services du fichier JAR/WAR. Il peut ensuite être découvert en utilisant la classe java.util.ServiceLoader qui, lorsqu'on lui donne une Class , générera des instances de toutes les sous-classes déclarées de cette classe (ou, si Class représente une interface, toutes les classes implémentant cette interface).

le principal avantage de cette approche est qu'il n'est pas nécessaire de scanner manuellement le chemin de classe entier pour les sous - classes- toute la logique de découverte est contenue dans la classe ServiceLoader , et il charge seulement les classes explicitement déclarées dans le répertoire META-INF/services (pas toutes les classes sur le chemin de la classe).

Il y a, cependant, quelques inconvénients:

  • il ne trouvera pas toutes les sous-classes , seulement celles qui sont explicitement déclarées. Par conséquent, si vous devez vraiment trouver toutes les sous-classes, cette approche peut être insuffisante.
  • il exige que le développeur déclare explicitement la classe dans le répertoire META-INF/services . C'est un fardeau supplémentaire sur le développeur, et peut être les risques d'erreur.
  • le ServiceLoader.iterator() génère des instances de sous-classe, pas leurs objets Class . Cela entraîne deux problèmes:
    • vous n'avez pas votre mot à dire sur la façon dont les sous - classes sont construites-le constructeur no-arg est utilisé pour créer les instances.
    • en tant que telles, les sous-classes doivent avoir un constructeur par défaut, ou doivent déclarer explicitement un constructeur sans arg.

Apparemment, Java 9 remédiera à certaines de ces lacunes (en particulier celles concernant l'instanciation des sous-classes).

Un Exemple

supposons que vous soyez intéressé à trouver des classes qui implémentent une interface com.example.Example :

package com.example;

public interface Example {
    public String getStr();
}

la classe com.example.ExampleImpl met en œuvre cette interface:

package com.example;

public class ExampleImpl implements Example {
    public String getStr() {
        return "ExampleImpl's string.";
    }
}

vous diriez que la classe ExampleImpl est une implémentation de Example en créant un fichier META-INF/services/com.example.Example contenant le texte com.example.ExampleImpl .

ensuite, vous pouvez obtenir une instance de chaque implémentation de Example (y compris une instance de ExampleImpl ) comme suit:

ServiceLoader<Example> loader = ServiceLoader.load(Example.class)
for (Example example : loader) {
    System.out.println(example.getStr());
}

// Prints "ExampleImpl's string.", plus whatever is returned
// by other declared implementations of com.example.Example.
2
répondu Mac 2017-09-25 00:50:19

Ajouter à une carte statique à l'intérieur (ce.getClass ().getName ()) le constructeur de classes parent (ou en créer un par défaut) mais il sera mis à jour à l'exécution. Si l'initialisation paresseuse est une option, vous pouvez essayer cette approche.

1
répondu Ravindranath Akila 2015-04-14 04:15:21

j'avais besoin de faire cela comme cas d'essai, pour voir si de nouvelles classes avaient été ajoutées au code. C'est ce que j'ai fait

final static File rootFolder = new File(SuperClass.class.getProtectionDomain().getCodeSource().getLocation().getPath());
private static ArrayList<String> files = new ArrayList<String>();
listFilesForFolder(rootFolder); 

@Test(timeout = 1000)
public void testNumberOfSubclasses(){
    ArrayList<String> listSubclasses = new ArrayList<>(files);
    listSubclasses.removeIf(s -> !s.contains("Superclass.class"));
    for(String subclass : listSubclasses){
        System.out.println(subclass);
    }
    assertTrue("You did not create a new subclass!", listSubclasses.size() >1);     
}

public static void listFilesForFolder(final File folder) {
    for (final File fileEntry : folder.listFiles()) {
        if (fileEntry.isDirectory()) {
            listFilesForFolder(fileEntry);
        } else {
            files.add(fileEntry.getName().toString());
        }
    }
}
0
répondu Cutetare 2018-04-20 16:51:31