web-dev-qa-db-fra.com

Comment trouvez-vous toutes les sous-classes d'une classe donnée en Java?

Comment peut-on s'y prendre et essayer de trouver toutes les sous-classes d'une classe donnée (ou tous les implémenteurs d'une interface donnée) en Java? 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. Chargez chaque classe et testez pour voir s'il s'agit d'une sous-classe ou d'un implémenteur de la classe ou de l'interface souhaitée.

Dans Eclipse, il existe une fonctionnalité intéressante appelée Hiérarchie des types qui parvient à montrer cela de manière assez efficace .

181
Avrom

Il n'y a pas d'autre moyen de le faire que ce que vous avez décrit. Pensez-y: comment peut-on savoir quelles classes étendent ClassX sans analyser chaque classe sur le classpath?

Eclipse ne peut vous renseigner que sur les super et sous-classes de ce qui semble être une durée "efficace", car toutes les données de type ont déjà été chargées au point où vous avez appuyé sur le bouton "Afficher dans la hiérarchie des types" compiler constamment vos classes, sait tout sur le chemin de classe, etc.).

68
matt b

L'analyse de classes n'est pas facile avec Java pur.

Le framework Spring propose une classe appelée ClassPathScanningCandidateComponentProvider pouvant faire ce dont vous avez besoin. L'exemple suivant trouverait toutes les sous-classes de MyClass dans le package org.example.package.

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 bytecode pour trouver les candidats, ce qui signifie qu’elle ne chargera pas toutes les classes qu’elle scanne.

120
fforw

Cela n'est pas possible uniquement à l'aide de l'API Java Reflections intégrée.

Il existe un projet qui effectue l'analyse et l'indexation nécessaires de votre chemin d'accès aux classes afin que vous puissiez accéder à ces informations ...

Reflets

Une analyse de métadonnées d'exécution Java, dans l'esprit de Scannotations

Reflections analyse votre chemin de classe, indexe les métadonnées, vous permet de l'interroger à l'exécution et peut éventuellement enregistrer et collecter ces informations pour de nombreux modules au sein de votre projet .

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

  • obtenir tous les sous-types d'un type
  • obtenir tous les types annotés avec une annotation
  • obtenir tous les types annotés avec une annotation, y compris les paramètres d'annotation correspondants
  • obtenir toutes les méthodes annotées avec certains

(disclaimer: je ne l'ai pas utilisé, mais la description du projet semble parfaitement adaptée à vos besoins.)

42
Mark Renouf

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

10
Rob

Je l'ai fait il y a plusieurs années. Le moyen le plus fiable de le faire (c'est-à-dire avec les API Java officielles et aucune dépendance externe) est d'écrire un doclet personnalisé pour générer une liste pouvant être lue au moment de l'exécution.

Vous pouvez l'exécuter depuis la ligne de commande comme ceci:

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

ou lancez-le 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 plus de simplicité, j'ai supprimé l'analyse des arguments en ligne de commande et j'écris dans System.out plutôt que dans un fichier.

8
David Leppik

Je sais que j'ai quelques années de retard en ce parti, 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 par programme, si vous écrivez un plug-in Eclipse (et tirez ainsi parti de leur mise en cache, etc.) pour rechercher des classes qui implémentent une interface. Voici ma première coupe (très approximative):

  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 n’attrape que les classes qui implémentent directement l’interface, pas toutes leurs sous-classes - mais une petite récursion ne fait de mal à personne.

7
Curtis

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 packages dans lesquels vous souhaitez effectuer la recherche (par exemple, "com.mycompany" ou même simplement "com"), et Superclass est votre supertype (cela fonctionne également sur les interfaces).

6
mikołak

Essayez ClassGraph . (Avertissement, je suis l'auteur). ClassGraph prend en charge l'analyse des sous-classes d'une classe donnée, au moment de l'exécution ou de la construction, mais aussi beaucoup plus. ClassGraph peut construire une représentation abstraite du graphique de classe entier (toutes les classes, annotations, méthodes, paramètres de méthode et champs) en mémoire, pour toutes les classes du chemin de classe ou pour les classes de packages sur liste blanche. Vous pouvez toutefois interroger ce graphique de classe. tu veux. ClassGraph prend en charge plus de mécanismes de spécification de chemin de classe et de chargeurs de classes que tout autre scanner, et fonctionne également de manière transparente avec le nouveau système de module JPMS. Ainsi, si vous basez votre code sur ClassGraph, votre code sera portable. Voir l'API ici.

6
Luke Hutchison

La différence entre votre implémentation et Eclipse s'explique par le fait que vous analysez chaque fois, tandis qu'Eclipse (et d'autres outils) n'analysent qu'une seule fois (pendant le chargement du projet) et créez un index. La prochaine fois que vous demanderez des données, elles ne seront pas numérisées à nouveau, mais regardez l'index.

3
OscarRyz

Il convient de noter également que cela ne trouvera bien sûr que toutes les sous-classes qui existent sur votre chemin de classe actuel. Vraisemblablement, cela convient à ce que vous regardez actuellement, et il y a de fortes chances que vous l'ayez envisagé, mais si à un moment quelconque vous avez publié une classe non -final dans la nature (pour différents niveaux de "sauvage"), il est tout à fait possible que quelqu'un d'autre a écrit sa propre sous-classe que vous ne saurez pas.

Ainsi, si vous vouliez voir toutes les sous-classes parce que vous voulez apporter un changement et que vous allez voir comment cela affecte le comportement des sous-classes, 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 devraient être bien documentées. Effectuez les modifications conformément à cette documentation sans changer la sémantique des méthodes/champs non privés et vos modifications doivent être compatibles avec les versions antérieures, pour toute sous-classe ayant au moins suivi votre définition de la super-classe.

3
Andrzej Doyle

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
Xin Meng

J'utilise une bibliothèque de réflexion, qui analyse votre chemin d'accès aux classes pour toutes les sous-classes: https://github.com/ronmamo/reflections

Voici comment cela se ferait:

Reflections reflections = new Reflections("my.project");
Set<Class<? extends SomeType>> subTypes = reflections.getSubTypesOf(SomeType.class);
2
futchas

En fonction de vos besoins particuliers, le mécanisme service loader de Java peut parfois vous aider.

En bref, cela permet aux développeurs de déclarer explicitement qu'une classe sous-classe une autre classe (ou implémente une interface) en la listant dans un fichier du répertoire META-INF/services du fichier JAR/WAR. Il peut ensuite être découvert à l'aide de la classe Java.util.ServiceLoader qui, lorsqu'un objet Class est créé, génère des instances de toutes les sous-classes déclarées de cette classe (ou, si la 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 d’analyser manuellement le chemin de classe complet pour les sous-classes: toute la logique de découverte est contenue dans la classe ServiceLoader et elle ne charge que les classes explicitement déclarées dans le répertoire META-INF/services (toutes les classes du répertoire classpath).

Il y a cependant quelques inconvénients:

  • Il ne trouvera pas les sous-classes all, mais uniquement celles qui sont explicitement déclarées. En tant que tel, si vous devez vraiment trouver toutes les sous-classes, cette approche peut être insuffisante.
  • Le développeur doit explicitement déclarer la classe dans le répertoire META-INF/services. Cela constitue une charge supplémentaire pour le développeur et peut être sujet à des erreurs.
  • ServiceLoader.iterator() génère des instances de sous-classe, pas leurs objets Class. Cela provoque deux problèmes:
    • Vous n'avez aucun mot à dire sur la construction des sous-classes - 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 explicitement déclarer un constructeur sans argument.

Apparemment, Java 9 résoudra certaines de ces lacunes (notamment celles concernant l’instanciation des sous-classes).

Un exemple

Supposons que vous souhaitiez 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 implémente cette interface:

package com.example;

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

Vous déclareriez 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
Mac

Ajoutez-les à une carte statique à l'intérieur (this.getClass (). GetName ()) du constructeur de la classe parent (ou créez-en un par défaut), mais celui-ci sera mis à jour à l'exécution. Si l’initialisation paresseuse est une option, vous pouvez essayer cette approche.

1
Ravindranath Akila

Je devais faire cela en tant que test, 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
Cutetare