Domanda

Come si fa a cercare di trovare tutte le sottoclassi di una determinata classe (o tutti gli implementatori di una determinata interfaccia) in Java?A partire da ora, ho un metodo per farlo, ma lo trovo abbastanza inefficiente (per non dire altro).Il metodo è:

  1. Ottieni un elenco di tutti i nomi delle classi presenti nel percorso della classe
  2. Carica ogni classe e verifica se è una sottoclasse o un implementatore della classe o interfaccia desiderata

In Eclipse c'è una bella funzionalità chiamata Gerarchia dei tipi che riesce a mostrarlo in modo abbastanza efficiente.Come si fa a farlo in modo programmatico?

È stato utile?

Soluzione

Non c'è altro modo per farlo se non quello che hai descritto. Pensaci: come si può sapere quali classi estendono ClassX senza eseguire la scansione di ogni classe sul percorso di classe?

Eclipse può solo parlarti delle super e delle sottoclassi in quello che sembra essere un " efficiente " tempo perché ha già tutti i dati di tipo caricati nel punto in cui si preme il " Visualizza in Type Hierarchy " pulsante (dal momento che compila costantemente le tue classi, conosce tutto sul percorso di classe, ecc.)

Altri suggerimenti

La scansione per le classi non è facile con Java puro.

Il framework di primavera offre una classe chiamata ClassPathScanningCandidateComponentProvider che può fare ciò di cui hai bisogno. L'esempio seguente troverà tutte le sottoclassi di MyClass nel pacchetto 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
}

Questo metodo ha l'ulteriore vantaggio di utilizzare un analizzatore di bytecode per trovare i candidati, il che significa che non caricherà tutte le classi che scansiona.

Non è possibile farlo utilizzando solo l'API Java Reflections integrata.

Esiste un progetto che esegue la scansione e l'indicizzazione necessarie del tuo percorso di classe in modo da poter accedere a queste informazioni ...

Reflections

Un'analisi dei metadati di runtime Java, nello spirito di Scannotations

Reflections esegue la scansione del percorso di classe, indicizza i metadati, consente di interrogarlo in fase di esecuzione e può salvare e raccogliere tali informazioni per molti moduli all'interno del progetto.

Utilizzando Reflections è possibile eseguire una query sui metadati per:

  • ottiene tutti i sottotipi di qualche tipo
  • annota tutti i tipi con alcune annotazioni
  • annota tutti i tipi con alcune annotazioni, inclusa la corrispondenza dei parametri di annotazione
  • ottiene tutti i metodi annotati con alcuni

(disclaimer: non l'ho usato, ma la descrizione del progetto sembra adattarsi perfettamente alle tue esigenze.)

Non dimenticare che il Javadoc generato per una classe includerà un elenco di noti sottoclassi (e per interfacce, classi di esecuzione note).

L'ho fatto diversi anni fa. Il modo più affidabile per farlo (cioè con API Java ufficiali e senza dipendenze esterne) è scrivere un doclet personalizzato per produrre un elenco che può essere letto in fase di esecuzione.

Puoi eseguirlo dalla riga di comando in questo modo:

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

o eseguilo da formica in questo modo:

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

Ecco il codice di 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;
        }
    }
}

Per semplicità, ho rimosso l'analisi dell'argomento della riga di comando e sto scrivendo su System.out anziché su un file.

So di essere in ritardo di qualche anno a questa festa, ma mi sono imbattuto in questa domanda cercando di risolvere lo stesso problema. Puoi utilizzare la ricerca interna di Eclipse a livello di codice, se stai scrivendo un plugin Eclipse (e quindi approfittare della loro memorizzazione nella cache, ecc.) Per trovare le classi che implementano un'interfaccia. Ecco il mio primo (molto approssimativo) primo taglio:

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

Il primo problema che vedo finora è che sto solo prendendo lezioni che implementano direttamente l'interfaccia, non tutte le loro sottoclassi - ma una piccola ricorsione non ha mai fatto male a nessuno.

Tenendo presente le limitazioni citate nelle altre risposte, puoi anche utilizzare openpojo's PojoClassFactory ( disponibile su Maven ) nel modo seguente :

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

Dove packageRoot è la stringa principale dei pacchetti in cui si desidera cercare (ad es. "com.mycompany" o anche solo "com") e Superclass è il supertipo (funziona anche su interfacce).

Prova ClassGraph . (Dichiarazione di non responsabilità, sono l'autore). ClassGraph supporta la scansione di sottoclassi di una determinata classe, in fase di esecuzione o in fase di creazione, ma anche molto altro. ClassGraph può creare una rappresentazione astratta dell'intero grafico di classe (tutte le classi, annotazioni, metodi, parametri del metodo e campi) in memoria, per tutte le classi sul percorso di classe o per le classi in pacchetti autorizzati e puoi comunque interrogare quel grafico di classe tu vuoi. ClassGraph supporta più meccanismi di specifica del classpath e classloader rispetto a qualsiasi altro scanner, e funziona perfettamente anche con il nuovo sistema di moduli JPMS, quindi se basi il tuo codice su ClassGraph, il tuo codice sarà al massimo portatile. Consulta l'API qui.

Va anche notato che questo ovviamente troverà solo tutte quelle sottoclassi che esistono sul tuo percorso di classe corrente.Presumibilmente questo va bene per quello che stai guardando attualmente, ed è probabile che tu lo abbia considerato, ma se in qualsiasi momento hai rilasciato un non-final class into the wild (per vari livelli di "selvaggio") allora è del tutto possibile che qualcun altro abbia scritto la propria sottoclasse di cui non sarai a conoscenza.

Pertanto, se ti è capitato di voler vedere tutte le sottoclassi perché vuoi apportare una modifica e vedere come influisce sul comportamento delle sottoclassi, tieni a mente le sottoclassi che non puoi vedere.Idealmente tutti i metodi non privati ​​e la classe stessa dovrebbero essere ben documentati;apporta modifiche in base a questa documentazione senza modificare la semantica dei metodi/campi non privati ​​e le tue modifiche dovrebbero essere compatibili con le versioni precedenti, almeno per qualsiasi sottoclasse che seguisse la tua definizione della superclasse.

Il motivo per cui vedi una differenza tra l'implementazione e Eclipse è perché esegui la scansione ogni volta, mentre Eclipse (e altri strumenti) esegue la scansione una sola volta (durante il caricamento del progetto la maggior parte delle volte) e crea un indice. La prossima volta che chiedi i dati, questi non scansionano più, ma guarda l'indice.

Scrivo solo una semplice demo per utilizzare org.reflections.Reflections per ottenere sottoclassi di classe astratta:

https://github.com/xmeng1/ReflectionsDemo

A seconda delle esigenze particolari, in alcuni casi Java meccanismo caricatore di servizio potrebbe realizzare ciò che stai cercando.

In breve, consente agli sviluppatori di dichiarare esplicitamente che una classe subclasse qualche altra classe (o implementa qualche interfaccia) elencandola in un file nella directory META-INF/services del file JAR / WAR. Può quindi essere scoperto usando java.util.ServiceLoader classe che, quando viene assegnato un oggetto Class, genererà istanze di tutte le sottoclassi dichiarate di quella classe (o, se ServiceLoader rappresenta un'interfaccia, tutte le classi che implementano tale interfaccia).

Il vantaggio principale di questo approccio è che non è necessario eseguire la scansione manuale dell'intero classpath alla ricerca di sottoclassi: tutta la logica di rilevamento è contenuta nella classe ServiceLoader.iterator() e carica solo le classi dichiarate esplicitamente nella com.example.Example directory (non tutte le classi sul percorso di classe).

Vi sono, tuttavia, alcuni svantaggi:

  • Non troverà tutte le sottoclassi , solo quelle dichiarate esplicitamente. Pertanto, se è necessario trovare davvero tutte le sottoclassi, questo approccio potrebbe essere insufficiente.
  • Richiede allo sviluppatore di dichiarare esplicitamente la classe nella directory com.example.ExampleImpl. Questo è un onere aggiuntivo per lo sviluppatore e può essere soggetto a errori.
  • ExampleImpl genera istanze di sottoclassi, non i loro Example oggetti. Ciò causa due problemi:
    • Non si può dire nulla su come sono costruite le sottoclassi: il costruttore no-arg è usato per creare le istanze.
    • In quanto tale, le sottoclassi devono avere un costruttore predefinito o esplicitamente dichiarare un costruttore no-arg.

Apparentemente Java 9 affronterà alcune di queste carenze (in particolare quelle relative all'istanza delle sottoclassi).

Un esempio

Supponi di essere interessato a trovare classi che implementano un'interfaccia META-INF/services/com.example.Example:

package com.example;

public interface Example {
    public String getStr();
}

La classe <=> implementa tale interfaccia:

package com.example;

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

Dichiareresti che la classe <=> è un'implementazione di <=> creando un file <=> contenente il testo <=>.

Quindi, è possibile ottenere un'istanza di ogni implementazione di <=> (inclusa un'istanza di <=>) come segue:

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.

Aggiungili a una mappa statica all'interno (this.getClass (). getName ()) del costruttore delle classi parent (o creane uno predefinito) ma questo verrà aggiornato in runtime. Se l'inizializzazione lazy è un'opzione, puoi provare questo approccio.

Sto usando una libreria di riflessione, che analizza il tuo percorso di classe per tutte le sottoclassi: https://github.com / ronmamo / riflessioni

Ecco come sarebbe fatto:

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

Avevo bisogno di farlo come test case, per vedere se al codice erano state aggiunte nuove classi. Questo è quello che ho fatto

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());
        }
    }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top