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:
Dans Eclipse, il existe une fonctionnalité intéressante appelée Hiérarchie des types qui parvient à montrer cela de manière assez efficace .
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.).
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.
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 ...
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.)
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).
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.
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.
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());
}
Où 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).
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.
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.
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.
Je viens d'écrire une démo simple pour utiliser le org.reflections.Reflections
pour obtenir des sous-classes de classe abstraite:
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);
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:
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: Apparemment, Java 9 résoudra certaines de ces lacunes (notamment celles concernant l’instanciation des sous-classes).
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.
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.
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());
}
}
}