Frage

Wie geht ein über und versuchen, alle Subklassen einer Klasse zu finden (oder alle Implementierer einer bestimmten Schnittstelle) in Java? Ab jetzt habe ich eine Methode, dies zu tun, aber ich finde es ziemlich ineffizient (gelinde gesagt). Das Verfahren ist:

  1. Hier finden Sie eine Liste aller Klassennamen, die auf dem Klassenpfad existieren
  2. Laden Sie jede Klasse und Test, um zu sehen, ob es eine Unterklasse oder Implementierer der gewünschten Klasse oder Schnittstelle
  3. ist

In Eclipse gibt es ein nettes Feature der Typ Hierarchie genannt, die diese sehr effizient zu zeigen, verwaltet. Wie geht ein über und tut es programmatisch?

War es hilfreich?

Lösung

Es gibt keinen anderen Weg, als es andere zu tun, was Sie beschrieben. Denken Sie darüber nach - wie jemand wissen kann, welche Klassen ClassX erweitern, ohne Abtasten jeder Klasse auf dem Classpath

Eclipse kann Ihnen nur sagen, über die Super- und Subklassen in dem, was scheint eine „effiziente“ Höhe der Zeit zu sein, weil sie bereits alle der Typ Daten hat an der Stelle geladen, wo Sie die „Anzeige in Typhierarchie“ drücken Sie die Taste ( da es ständig Ihre Klassen kompiliert, weiß über alles auf dem classpath, etc).

Andere Tipps

Scannen für Klassen sind nicht einfach mit reinem Java.

Die Feder Framework bietet eine Klasse namens ClassPathScanningCandidateComponentProvider dass tun können, was Sie brauchen. Das folgende Beispiel alle Subklassen von MyClass im Paket org.example.package finden würde

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
}

Dieses Verfahren hat den zusätzlichen Vorteil, einen Bytecode-Analysator mit den Kandidaten zu finden, was bedeutet, es wird nicht alle Klassen lädt scannt.

Dies ist nicht möglich mit zu tun, dem einzigen integrierten Java Reflections API.

Ein Projekt besteht das macht die notwendigen Scannen und Indexieren Ihres Classpath so können Sie den Zugriff diese Informationen erhalten ...

Reflections

eine Java-Runtime-Metadatenanalyse, im Geist der Scannotations

Reflections sucht Ihre Classpath, indiziert die Metadaten, können Sie es zur Laufzeit abzufragen und diese Informationen für viele Module in Ihrem Projekt speichern und sammeln kann.

Mit Reflections Sie Ihre Metadaten abfragen können:

  • erhalten alle Subtypen von irgendeiner Art
  • erhalten alle Typen mit einiger Anmerkung kommentieren
  • erhalten alle Typen mit einiger Anmerkung kommentiert, einschließlich Annotation Parameter passend
  • alle Methoden erhalten mit einigen Anmerkungen versehen

(Disclaimer:. Ich habe es nicht benutzt, aber die Beschreibung des Projektes scheint eine genaue Passform für Ihre Bedürfnisse zu sein)

Vergessen Sie nicht, dass die erzeugte Javadoc für eine Klasse eine Liste von bekannten umfassen wird Unterklassen (und für deren Schnittstellen, bekannt implementierenden Klassen).

habe ich das vor einigen Jahren. Der zuverlässigste Weg, dies zu tun (das heißt mit dem offiziellen Java-APIs und keine externen Abhängigkeiten) ist eine benutzerdefinierte doclet zu schreiben, um eine Liste zu erzeugen, die zur Laufzeit gelesen werden können.

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

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

oder von Ameise wie folgt ausführen:

<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 Befehlszeilenargument Parsing entfernt und ich schreibe auf System.out anstatt einer Datei.

Ich weiß, ich bin ein paar Jahre zu spät zu dieser Party, aber ich auf diese Frage kam versuchen, das gleiche Problem zu lösen. Sie können programmatisch Eclipse interne Suche verwenden, wenn Sie eine Eclipse-Plugin gerade schreiben (und damit die Vorteile ihrer Caching nehmen, usw.), Klassen zu finden, die eine Schnittstelle zu implementieren. Hier ist mein (sehr grob) 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 die Schnittstelle direkt implementieren, nicht alle ihre Unterklassen - aber ein wenig Rekursion nie jemand verletzt

.

unter Berücksichtigung der Einschränkungen in den anderen Antworten erwähnt, können Sie auch openpojo des PojoClassFactory ( auf Maven ) auf die folgende Weise:

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

Wo packageRoot ist die Wurzel String den Sie Pakete wollen in (z "com.mycompany" oder sogar "com" nur) suchen, und Superclass ist Ihr Supertyp (dies funktioniert auf Schnittstellen als auch).

Versuchen Sie ClassGraph . (Disclaimer: Ich bin der Autor). ClassGraph unterstützt das Scannen für Subklassen einer Klasse, entweder zur Laufzeit oder bei der Erstellung, sondern auch vieles mehr. ClassGraph kann eine abstrakte Darstellung der gesamten Klasse Graph (alle Klassen, Anmerkungen, Methoden, Methodenparameter und Felder) im Speicher, für alle Klassen auf dem Classpath oder für Klassen in der weißen Liste Paketen bauen, und Sie diese Klasse Graph jedoch abfragen Sie wollen. ClassGraph unterstützt mehr Classpath Spezifikation Mechanismen und Classloader als jeder andere Scanner und auch arbeitet nahtlos mit dem neuen JPMS Modulsystem, so dass, wenn Sie Ihren Code auf ClassGraph stützen, wird Ihr Code maximal tragbar sein. hier die API.

Es sollte auch darauf hingewiesen, dass dies natürlich nur all jene Unterklassen finden, die auf Ihrem aktuellen Classpath existieren. Vermutlich ist dies OK für das, was Sie gerade betrachten, und die Chancen sind Sie dies hielt, aber wenn Sie an einer beliebigen Stelle haben eine nicht-final Klasse in die Freiheit entlassen (für unterschiedliche Ebenen der „wild“), dann ist es durchaus möglich, dass jemand anderes ihre eigene Unterklasse geschrieben hat, dass Sie nicht wissen.

Wenn Sie also alle Unterklassen zu sehen sein zu wollen passiert, weil Sie eine Änderung vornehmen möchten und gehen zu sehen, wie es Unterklassen Verhalten beeinflusst - dann die Unterklassen bedenken, dass man nicht sehen kann. Im Idealfall alle Ihre nicht-privaten Methoden, und die Klasse selbst gut dokumentiert werden soll; Änderungen nach dieser Dokumentation ohne Änderung die Semantik der Methoden / nicht-privater Felder und Ihre Änderungen sollten abwärtskompatibel sein, für jede Unterklasse, die Ihre Definition der übergeordneten Klasse mindestens gefolgt.

Der Grund, warum Du einen Unterschied zwischen der Implementierung und Eclipse zu sehen ist, weil Sie jedes Mal scannen, während Eclipse-(und andere Tools) nur einmal scannen (während der Projektlast der meisten Zeit) und einen Index erstellen. Nächstes Mal, wenn die Daten verlangen es nicht wieder scannen, aber Blick auf den Index.

Ich schreibe nur eine einfache Demo die org.reflections.Reflections verwenden Unterklassen der abstrakten Klasse zu erhalten:

https://github.com/xmeng1/ReflectionsDemo

Je nach Ihren speziellen Anforderungen in einigen Fällen Java Service loader Mechanismus könnte erreichen, was Sie wollen.

Kurz gesagt, ermöglicht es Entwickler ausdrücklich zu erklären, dass eine Klasse eine andere Klasse Unterklassen (oder implementiert eine Schnittstelle), indem sie in einer Datei in der JAR / WAR-Datei der META-INF/services Verzeichnisliste. Es kann dann entdeckt werden, mit dem java.util.ServiceLoader Klasse, die, wenn ein Class Objekt gegeben wird Instanzen aller deklarierten Unterklassen dieser Klasse erzeugen (oder, wenn der Class stellt eine Schnittstelle, alle Klassen, die Schnittstelle implementiert).

Der Hauptvorteil dieses Ansatzes besteht darin, dass es keine Notwendigkeit, manuell ist die gesamte Classpath für Unterklassen zu scannen - all die Entdeckung Logik innerhalb der ServiceLoader Klasse enthalten ist, und es lädt nur die Klassen explizit im META-INF/services Verzeichnis deklarieren (nicht jeder Klasse auf dem classpath).

Es gibt jedoch einige Nachteile auf:

  • Es wird nicht gefunden alle Unterklassen, nur diejenigen, die explizit deklariert werden. Als solcher, wenn Sie wirklich alle Unterklassen finden müssen, kann dieser Ansatz nicht ausreichend sein.
  • Es erfordert die Entwickler explizit die Klasse unter dem META-INF/services Verzeichnis zu deklarieren. Dies ist eine zusätzliche Belastung für die Entwickler und kann fehleranfällig sein.
  • Die ServiceLoader.iterator() erzeugt Unterklasse Instanzen, nicht ihre Class Objekte. Dies führt zu zwei Problemen:
    • Sie erhalten nicht sagen, wie die Unterklassen aufgebaut sind -. Der nicht-arg Konstruktor verwendet wird, um die Instanzen zu erstellen
    • Als solche müssen die Unterklassen einen Standardkonstruktor haben, oder müssen explizit ein nicht-arg Konstruktor deklarieren.

Anscheinend Java 9 werden einige dieser Mängel (insbesondere die, die in Bezug auf Instanziierung von Unterklassen) werden Adressierung.

Ein Beispiel

Angenommen, Sie bei der Suche nach Klassen interessiert sind, die eine Schnittstelle com.example.Example implementieren:

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ürde erklären, die Klasse ExampleImpl ist eine Implementierung von Example durch eine Datei META-INF/services/com.example.Example Erstellung des Text com.example.ExampleImpl enthält.

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

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.

Fügen Sie sie zu einer statischen Karte innen (this.getClass (). GetName ()) der übergeordneten Klassen Konstruktor (oder eine Standard erstellen), aber dies wird im laufenden Betrieb aktualisiert. Wenn faul Initialisierung kann eine Option, die Sie versuchen, diesen Ansatz.

Ich bin mit einem Reflexions lib, die für alle Unterklassen Classpath sucht: https://github.com / ronmamo / Reflexionen

Dies ist, wie wäre es geschehen:

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

Ich brauchte diese als Testfall zu tun, um zu sehen, ob neue Klassen zum Code hinzugefügt worden waren. Das ist, was ich tat

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());
        }
    }
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top