web-dev-qa-db-de.com

Wie finden Sie alle Unterklassen einer bestimmten Klasse in Java?

Wie geht man vor und versucht, in Java alle Unterklassen einer bestimmten Klasse (oder aller Implementierer einer bestimmten Schnittstelle) zu finden? am wenigsten sagen .. .. Die Methode ist: 

  1. Rufen Sie eine Liste aller Klassennamen ab, die im Klassenpfad vorhanden sind
  2. Laden Sie jede Klasse und testen Sie, ob es sich um eine Unterklasse oder Implementierer der gewünschten Klasse oder Schnittstelle handelt

In Eclipse gibt es eine Nizza-Funktion, die Typhierarchie, die das recht effizient zeigt. Wie geht man programmgesteuert vor?

181
Avrom

Es gibt keine andere Möglichkeit als das, was Sie beschrieben haben. Denken Sie darüber nach - wie kann jeder wissen, welche Klassen ClassX erweitern, ohne jede Klasse im Klassenpfad zu scannen?

Eclipse kann Sie nur in einer scheinbar "effizienten" Zeitspanne über Super- und Unterklassen informieren, da an diesem Punkt bereits alle Typdaten geladen sind, an denen Sie die Schaltfläche "In Typhierarchie anzeigen" drücken (da sie vorhanden ist Ihre Klassen ständig zusammenstellen, wissen alles über den Klassenpfad usw.).

68
matt b

Das Scannen nach Klassen ist mit reinem Java nicht einfach. 

Das Frühlings-Framework bietet eine Klasse namens ClassPathScanningCandidateComponentProvider , die alles kann, was Sie brauchen. Das folgende Beispiel würde alle Unterklassen von MyClass im Paket org.example.package finden

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
}

Diese Methode hat den zusätzlichen Vorteil, dass ein Bytecode-Analysator verwendet wird, um die Kandidaten zu finden. Dies bedeutet, dass nicht alle von ihr gescannten Klassen lädt.

114
fforw

Dies ist nicht möglich, wenn nur die integrierte Java Reflections-API verwendet wird.

Es gibt ein Projekt, das die notwendigen Überprüfungen und Indizierungen Ihres Klassenpfads vornimmt, damit Sie auf diese Informationen zugreifen können.

Reflexionen

Eine Java-Laufzeit-Metadatenanalyse im Sinne von Scannotations

Reflections durchsucht Ihren Klassenpfad, indiziert die Metadaten, ermöglicht die Abfrage zur Laufzeit und speichert und sammelt diese Informationen möglicherweise für viele Module in Ihrem Projekt.

Mit Reflections können Sie Ihre Metadaten nach folgenden Kriterien abfragen:

  • holen Sie sich alle Untertypen eines Typs
  • alle Typen werden mit Anmerkungen versehen
  • alle Typen werden mit Anmerkungen versehen, einschließlich Annotationsparameter
  • alle Methoden werden mit einigen Anmerkungen versehen

(Haftungsausschluss: Ich habe es nicht verwendet, aber die Beschreibung des Projekts scheint genau auf Ihre Bedürfnisse zugeschnitten zu sein.)

42
Mark Renouf

Vergessen Sie nicht, dass das generierte Javadoc für eine Klasse eine Liste bekannter Unterklassen (und für Schnittstellen bekannte Implementierungsklassen) enthält.

10
Rob

Ich habe das vor einigen Jahren gemacht. Der zuverlässigste Weg dazu (d. H. Mit offiziellen Java-APIs und ohne externe Abhängigkeiten) besteht darin, ein benutzerdefiniertes Doclet zu schreiben, um eine Liste zu erstellen, die zur Laufzeit gelesen werden kann.

Sie können es wie folgt von der Befehlszeile aus ausführen:

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

oder lass es von der Ameise so laufen:

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

Hier ist der grundlegende Code:

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;
        }
    }
}

Der Einfachheit halber habe ich das Parsen von Befehlszeilenargumenten entfernt und schreibe in System.out und nicht in eine Datei.

8
David Leppik

Ich weiß, ich bin ein paar Jahre zu spät zu dieser Party, aber ich bin auf diese Frage gestoßen und habe versucht, das gleiche Problem zu lösen. Sie können die interne Suche von Eclipse programmgesteuert verwenden, wenn Sie ein Eclipse-Plugin schreiben (und somit das Caching usw. nutzen), um Klassen zu finden, die eine Schnittstelle implementieren. Hier ist mein (sehr rauer) erster Schnitt:

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

Das erste Problem, das ich bisher sehe, ist, dass ich nur Klassen fange, die das Interface direkt implementieren, nicht alle Unterklassen -, aber eine kleine Rekursion hat niemanden verletzt.

7
Curtis

Unter Berücksichtigung der Einschränkungen, die in den anderen Antworten erwähnt wurden, können Sie auch openpojos PojoClassFactory ( verfügbar auf Maven ) auf folgende Weise verwenden:

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

Dabei ist packageRoot der Root-String der Pakete, in denen gesucht werden soll (z. B. "com.mycompany" oder auch nur "com"), und Superclass ist Ihr Supertyp (dies funktioniert auch bei Schnittstellen).

6
mikołak

Versuchen Sie ClassGraph . (Haftungsausschluss, ich bin der Autor). ClassGraph unterstützt die Suche nach Unterklassen einer bestimmten Klasse, entweder zur Laufzeit oder zur Erstellungszeit, aber auch viel mehr. ClassGraph kann eine abstrakte Darstellung des gesamten Klassendiagramms (aller Klassen, Anmerkungen, Methoden, Methodenparameter und Felder) im Speicher für alle Klassen im Klassenpfad oder für Klassen in Whitelist-Paketen erstellen. Sie können dieses Klassendiagramm jedoch abfragen Sie wollen. ClassGraph unterstützt mehr Classpath-Spezifikationsmechanismen und Classloader als jeder andere Scanner und funktioniert auch nahtlos mit dem neuen JPMS-Modulsystem. Wenn Sie also Ihren Code auf ClassGraph basieren, ist Ihr Code maximal portierbar. Siehe die API hier.

6
Luke Hutchison

Der Grund für den Unterschied zwischen Ihrer Implementierung und Eclipse besteht darin, dass Sie jedes Mal scannen, während Eclipse (und andere Tools) nur einmal scannen (während des Projekts meistens geladen) und einen Index erstellen. Wenn Sie das nächste Mal nach den Daten fragen, werden sie nicht mehr gescannt, sondern schauen Sie sich den Index an.

3
OscarRyz

Es sollte auch beachtet werden, dass dies natürlich nur alle Unterklassen findet, die in Ihrem aktuellen Klassenpfad vorhanden sind. Vermutlich ist dies für das, was Sie gerade betrachten, in Ordnung, und die Chancen stehen gut, dass Sie dies in Betracht gezogen haben, aber wenn Sie zu irgendeinem Zeitpunkt eine nicht-final-Klasse in die Wildnis freigegeben haben (für verschiedene Ebenen von "wild"), ist dies durchaus machbar dass jemand anderes seine eigene Unterklasse geschrieben hat, von der Sie nichts wissen werden.

Wenn Sie also alle Unterklassen sehen wollten, weil Sie eine Änderung vornehmen möchten und sehen möchten, wie sich das Verhalten der Unterklassen auswirkt, sollten Sie die Unterklassen berücksichtigen, die Sie nicht sehen können. Idealerweise sollten alle Ihre nicht privaten Methoden und die Klasse selbst gut dokumentiert sein. Nehmen Sie gemäß dieser Dokumentation Änderungen vor, ohne die Semantik von Methoden/nicht-privaten Feldern zu ändern, und Ihre Änderungen sollten für alle Unterklassen, die Ihrer Definition der Oberklasse folgten, abwärtskompatibel sein.

3
Andrzej Doyle

Ich schreibe einfach eine einfache Demo, um mit org.reflections.Reflections Unterklassen der abstrakten Klasse zu erhalten:

https://github.com/xmeng1/ReflectionsDemo

3
Xin Meng

Ich verwende eine Reflection-Bibliothek, die Ihren Klassenpfad nach allen Unterklassen durchsucht: https://github.com/ronmamo/reflections

So würde es gemacht werden:

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

Abhängig von Ihren speziellen Anforderungen kann Java Service Loader in einigen Fällen das erreichen, was Sie suchen.

Kurz gesagt: Entwickler können explizit deklarieren, dass eine Klasse eine andere Klasse (oder eine Schnittstelle) implementiert, indem sie in einer Datei im Verzeichnis META-INF/services der JAR/WAR-Datei aufgeführt wird. Sie kann dann mithilfe der Klasse Java.util.ServiceLoader ermittelt werden, die bei Angabe eines Class-Objekts Instanzen aller deklarierten Unterklassen dieser Klasse generiert (oder, falls die Class eine Schnittstelle darstellt, alle Klassen, die diese Schnittstelle implementieren).

Der Hauptvorteil dieses Ansatzes besteht darin, dass der gesamte Klassenpfad nicht manuell nach Unterklassen durchsucht werden muss. Die gesamte Erkennungslogik ist in der Klasse ServiceLoader enthalten. Sie lädt nur die explizit deklarierten Klassen im Verzeichnis META-INF/services (nicht jede Klasse in der Klasse Klassenpfad).

Es gibt jedoch einige Nachteile:

  • All Unterklassen wird nicht gefunden, nur die explizit deklariert. Wenn Sie also wirklich alle Unterklassen finden müssen, kann dieser Ansatz unzureichend sein.
  • Der Entwickler muss die Klasse explizit im Verzeichnis META-INF/services deklarieren. Dies ist eine zusätzliche Belastung für den Entwickler und kann fehleranfällig sein.
  • Die ServiceLoader.iterator() generiert Unterklasseninstanzen, nicht ihre Class-Objekte. Dies verursacht zwei Probleme:
    • Sie können nicht sagen, wie die Unterklassen erstellt werden. Der Konstruktor no-arg wird zum Erstellen der Instanzen verwendet.
    • Daher müssen die Unterklassen einen Standardkonstruktor haben oder explizit einen No-Arg-Konstruktor deklarieren.

Anscheinend wird Java 9 auf einige dieser Mängel eingehen (insbesondere auf die Instantiierung von Unterklassen).

Ein Beispiel

Angenommen, Sie sind an Klassen interessiert, die eine Schnittstelle implementieren. com.example.Example:

package com.example;

public interface Example {
    public String getStr();
}

Die Klasse com.example.ExampleImpl implementiert diese Schnittstelle:

package com.example;

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

Sie würden die Klasse ExampleImpl als Implementierung von Example deklarieren, indem Sie eine Datei META-INF/services/com.example.Example erstellen, die den Text com.example.ExampleImpl enthält.

Dann können Sie eine Instanz jeder Implementierung von Example (einschließlich einer Instanz von ExampleImpl) wie folgt erhalten:

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

Fügen Sie sie zu einer statischen Map (this.getClass (). GetName ()) des Konstruktors für übergeordnete Klassen hinzu (oder erstellen Sie einen Standardkonfigurator). Dies wird jedoch zur Laufzeit aktualisiert. Wenn eine langsame Initialisierung eine Option ist, können Sie diesen Ansatz ausprobieren.

1

Ich musste dies als Testfall tun, um zu sehen, ob dem Code neue Klassen hinzugefügt wurden. Das habe ich getan

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