Jak znaleźć wszystkie podklasy danej klasy w Javie?

Jak można znaleźć wszystkie podklasy danej klasy (lub wszystkich implementatorów danego interfejsu) w Javie? Jak na razie, mam metodę, aby to zrobić, ale uważam, że to dość nieefektywne(co najmniej). Metoda jest:

  1. uzyskaj listę wszystkich nazw klas, które istnieją na ścieżce klasy
  2. załaduj każdą klasę i przetestuj, czy jest to podklasa lub implementator pożądanej klasy lub interfejsu

W Eclipse jest fajna funkcja zwana typem Hierarchii, która potrafi to dość skutecznie pokazać. Jak to się robi i robi to programowo?

Author: guerda, 2009-01-29

15 answers

Nie ma innego sposobu, aby to zrobić, niż to, co opisałeś. Pomyśl o tym-skąd ktoś może wiedzieć, jakie klasy rozszerzają ClassX bez skanowania każdej klasy na classpath?

Eclipse może Ci tylko powiedzieć o super i podklasach w "wydajnym" czasie, ponieważ ma już wszystkie dane typu wczytane w momencie, w którym naciśniesz przycisk" Wyświetl w hierarchii typów " (ponieważ stale kompiluje Twoje klasy, wie o wszystkim na serwerze). classpath, etc).

 67
Author: matt b,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-29 15:58:52

Skanowanie klas nie jest łatwe z czystą Javą.

Framework spring oferuje klasę o nazwie ClassPathScanningCandidateComponentprovider , która może zrobić to, czego potrzebujesz. Poniższy przykład znajdzie wszystkie podklasy MyClass w pakiecie org.przykład.pakiet

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
}

Ta metoda ma dodatkową zaletę użycia analizatora kodu bajtowego, aby znaleźć kandydatów, co oznacza, że nie załaduje wszystkich klas, które skanuje.

 107
Author: fforw,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-12 16:15:12

Nie jest to możliwe przy użyciu tylko wbudowanego Java Reflections API.

Istnieje projekt, który wykonuje niezbędne skanowanie i indeksowanie Twojej ścieżki klasowej, abyś mógł uzyskać dostęp do tych informacji...

Refleksje

Analiza metadanych środowiska Java runtime w duchu skanowania

Reflections skanuje ścieżkę classpath, indeksuje metadane, pozwala na odpytywanie jej w trybie runtime i może zapisywać i zbierać te informacje dla wielu modułów w ramach Twojego projektu.

Za pomocą Reflections możesz odpytywać swoje metadane o:

  • get all subtypes of some type
  • get all types annotated with some adnotation
  • get all types annotated with some adnotation, including annotation parameters pasujących
  • get all methods annotated with some

(disclaimer: nie używałem go, ale Opis projektu wydaje się być dokładnie dopasowany do Twoich potrzeb.)

 42
Author: Mark Renouf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-29 16:24:52

Nie zapominaj, że wygenerowany Javadoc dla klasy będzie zawierał listę znanych podklas (a dla interfejsów-znanych klas wykonawczych).

 10
Author: Rob,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-29 22:57:11

Zrobiłem to kilka lat temu. Najbardziej niezawodnym sposobem na to (tj. z oficjalnymi API Javy i bez zewnętrznych zależności)jest napisanie niestandardowego docletu, aby stworzyć listę, która może być odczytana w czasie wykonywania.

Możesz go uruchomić z linii poleceń w następujący sposób:

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

Lub uruchom go z ant w ten sposób:

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

Oto Podstawowy kod:

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

Dla uproszczenia usunąłem parsowanie argumentów linii poleceń i piszę do systemu.out zamiast pliku.

 8
Author: David Leppik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-14 21:08:28

Wiem, że spóźniłem się kilka lat na tę imprezę, ale natknąłem się na to pytanie próbując rozwiązać ten sam problem. Możesz użyć wewnętrznego programu wyszukiwania Eclipse, jeśli piszesz wtyczkę Eclipse (i tym samym skorzystać z ich buforowania itp.), aby znaleźć klasy, które implementują interfejs. Oto mój (bardzo szorstki) pierwszy krój:

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

Pierwszy problem jaki widzę na razie jest taki, że wyłapuję tylko klasy, które bezpośrednio implementują interfejs, a nie wszystkie ich podklasy-ale mała rekursja nigdy nikogo nie skrzywdziła.

 7
Author: Curtis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-20 19:29:56

Pamiętając o ograniczeniach wymienionych w innych odpowiedziach, możesz również użyć openpojo ' s PojoClassFactory (dostępne na Maven ) w następujący sposób:

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

Gdzie packageRoot jest głównym ciągiem pakietów, w których chcesz wyszukać (np. "com.mycompany" lub nawet tylko "com"), a {[5] } jest Twoim supertype (działa to również na interfejsach).

 5
Author: mikołak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-11 18:34:56

Należy również zauważyć, że to oczywiście znajdzie tylko te wszystkie podklasy, które istnieją na bieżącej ścieżce klas. Prawdopodobnie jest to w porządku dla tego, na co obecnie patrzysz, i są szanse, że to rozważyłeś, ale jeśli w dowolnym momencie wypuściłeś klasę Nie - final do dziczy (dla różnych poziomów "dziczy"), to jest całkowicie wykonalne, że ktoś inny napisał własną podklasę, o której nie będziesz wiedział.

Tak więc, jeśli zdarzyło ci się chcieć zobaczyć wszystkie podklasy ponieważ chcesz dokonać zmiany i chcesz zobaczyć, jak to wpływa na zachowanie podklas - pamiętaj o podklasach, których nie widzisz. W idealnym przypadku wszystkie Nie-prywatne metody i sama klasa powinny być dobrze udokumentowane; dokonuj zmian zgodnie z tą dokumentacją bez zmiany semantyki metod/pól Nie-prywatnych, a twoje zmiany powinny być kompatybilne wstecz, przynajmniej dla każdej podklasy, która podążała za Twoją definicją superklasy.

 3
Author: Andrzej Doyle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-29 16:31:31

Powodem, dla którego widzisz różnicę między implementacją a Eclipse jest to, że skanujesz za każdym razem, podczas gdy Eclipse (i inne narzędzia) skanuje tylko raz (podczas ładowania projektu najczęściej) i tworzy indeks. Następnym razem, gdy poprosisz o dane, nie skanuje się ponownie, ale spójrz na indeks.

 3
Author: OscarRyz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-03 11:16:16

Po prostu piszę proste demo, aby użyć org.reflections.Reflections, aby uzyskać podklasy klasy abstrakcyjnej:

Https://github.com/xmeng1/ReflectionsDemo

 3
Author: Xin Meng,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-02 17:32:36

Spróbuj ClassGraph . (Zastrzeżenie, jestem autorem). ClassGraph obsługuje skanowanie podklas danej klasy, zarówno w czasie wykonywania, jak i kompilacji, ale także wiele więcej. ClassGraph może zbudować abstrakcyjną reprezentację całego wykresu klas (wszystkich klas, adnotacji, metod, parametrów metod i pól) w pamięci, dla wszystkich klas na ścieżce classpath lub dla klas w pakietach umieszczonych na białej liście. ClassGraph obsługuje więcej mechanizmy specyfikacji classpath i classloadery Niż jakikolwiek inny skaner, a także działa bezproblemowo z nowym systemem modułów JPMS, więc jeśli opierasz swój kod na ClassGraph, Twój kod będzie maksymalnie przenośny. Zobacz API tutaj.

 3
Author: Luke Hutchison,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-01 04:12:47

Używam lib reflection, która skanuje twoją ścieżkę klasową w poszukiwaniu wszystkich podklas: https://github.com/ronmamo/reflections

Tak by to było zrobione:

Reflections reflections = new Reflections("my.project");
Set<Class<? extends SomeType>> subTypes = reflections.getSubTypesOf(SomeType.class);
 2
Author: futchas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-03 08:48:39

W zależności od twoich szczególnych wymagań, w niektórych przypadkach mechanizm service loader Javy może osiągnąć to, czego szukasz.

W skrócie, pozwala programistom jawnie zadeklarować, że Klasa podklasa jakąś inną klasę (lub implementuje jakiś interfejs), wymieniając ją w pliku w katalogu META-INF/services pliku JAR/WAR. Można go następnie odkryć za pomocą java.util.ServiceLoader klasa, która po podaniu obiektu Class wygeneruje instancje wszystkich zadeklarowanych podklas ta klasa (lub, jeśli Class reprezentuje interfejs, wszystkie klasy implementujące ten interfejs).

Główną zaletą tego podejścia jest to, że nie ma potrzeby ręcznego skanowania całej ścieżki klas w poszukiwaniu podklas - Cała logika discovery jest zawarta w klasie ServiceLoader i ładuje tylko klasy jawnie zadeklarowane w katalogu META-INF/services (nie każda klasa na ścieżce klas).

Są jednak pewne wady:

  • Nie znajdzie wszystkie podklasy, tylko te, które są jawnie zadeklarowane. Jako takie, jeśli trzeba naprawdę znaleźć wszystkie podklasy, takie podejście może być niewystarczające.
  • wymaga od dewelopera jawnego zadeklarowania klasy w katalogu META-INF/services. Jest to dodatkowe obciążenie dla dewelopera i może być podatne na błędy.
  • ServiceLoader.iterator() generuje instancje podklasy, a nie ich obiekty Class. Powoduje to dwa problemy:
    • nie masz nic do powiedzenia na temat budowy podklas - do tworzenia instancji służy konstruktor no-arg.
    • jako takie, podklasy muszą mieć konstruktor domyślny lub muszą explicity zadeklarować konstruktor no-arg.

Najwyraźniej Java 9 zajmie się niektórymi z tych niedociągnięć (w szczególności tymi dotyczącymi tworzenia instancji podklas).

Przykład

Załóżmy, że jesteś zainteresowany znalezieniem klas, które implementują interfejs com.example.Example:

package com.example;

public interface Example {
    public String getStr();
}

Klasa com.example.ExampleImpl implementuje ten interfejs:

package com.example;

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

Klasa ExampleImpl jest implementacją Example, tworząc plik META-INF/services/com.example.Example zawierający tekst com.example.ExampleImpl.

Następnie można uzyskać instancję każdej implementacji Example (w tym instancję ExampleImpl) w następujący sposób:

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
Author: Mac,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-25 00:50:19

Dodaj je do statycznej mapy wewnątrz (this.getClass().getName()) konstruktor klas nadrzędnych (lub utwórz domyślną), ale zostanie to zaktualizowane w trybie runtime. Jeśli leniwa inicjalizacja jest opcją, Możesz spróbować tego podejścia.

 1
Author: Ravindranath Akila,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-14 04:15:21

Musiałem to zrobić jako przypadek testowy, aby sprawdzić, czy nowe klasy zostały dodane do kodu. This is what I did

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
Author: Cutetare,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-20 16:51:31