Java'da belirli bir sınıfın tüm alt sınıflarını nasıl bulabilirsiniz?


207

Java'da belirli bir sınıfın tüm alt sınıflarını (veya belirli bir arabirimin tüm uygulayıcılarını) nasıl bulmaya çalışır? Şu andan itibaren bunu yapmak için bir yöntemim var, ama oldukça verimsiz buluyorum (en azından söylemek gerekirse). Yöntem:

  1. Sınıf yolunda var olan tüm sınıf adlarının bir listesini alın
  2. Her sınıfı yükleyin ve istenen sınıfın veya arayüzün bir alt sınıfı mı yoksa uygulayıcısı mı olduğunu test edin

Eclipse'de, bunu oldukça verimli bir şekilde göstermeyi başaran Tür Hiyerarşisi adı verilen hoş bir özellik var. Kişi nasıl programlanır?


1
Yansımalar ve İlkbahar'a dayanan çözüm ilginç görünse de, bağımlılıklara sahip olmayan basit bir çözüme ihtiyacım vardı. Görünüşe göre benim orijinal kod (bazı tweaks ile) gitmek için yol oldu.
Avrom

1
Elbette getSupeClass yöntemini özyinelemeli olarak kullanabilirsiniz?

Özellikle belirli bir sınıfın tüm alt sınıflarını arıyordum . getSuperClass, bir sınıfın hangi alt sınıflarına sahip olduğunu söylemez, yalnızca belirli bir alt sınıf için hemen süper sınıfı alır. Ayrıca, IsAssignableFrom on Class yöntemi önerileriniz için daha uygundur (özyinelemeye gerek yoktur).
Avrom

Bu soru diğer birçok kopyadan bağlantılıdır, ancak yararlı, sade bir Java yanıtı içermez. Sigh ...
Eric Duminil

Yanıtlar:


77

Bunu tarif ettiğinizden başka yapmanın başka bir yolu yoktur. Bir düşünün - sınıfta her sınıfı taramadan hangi sınıfların ClassX'i genişlettiğini nasıl bilebilir?

Eclipse size yalnızca "verimli" bir süre gibi görünen süper ve alt sınıfları anlatabilir, çünkü "Tür Hiyerarşisinde Görüntüle" düğmesine bastığınız noktada zaten tüm tür verileri yüklüdür (çünkü sınıflarınızı sürekli derlemek, sınıf yolundaki her şeyi bilir, vb.).


23
Artık bu ve diğer yaygın yansıma görevlerine yardımcı olan org.reflections adlı basit bir kütüphane var . Bu kütüphane ile sadece reflections.getSubTypesOf(aClazz)) bağlantıyı
Enwired

@matt b - tüm sınıfları taraması gerekiyorsa, projenizde çok fazla sınıfınız olduğunda, ancak bunlardan sadece birkaçı sınıfınızı alt sınıflandırıyor olsa bile, performansta bir düşüş olduğu anlamına mı geliyor?
LeTex

Kesinlikle. Sadece tüm sınıflara dokunur. Kendi tarayıcınızı tanımlayarak, sınıfınızın genişletilmeyeceğini bildiğiniz belirli paketleri hariç tutmak gibi hızlandırabilirsiniz veya sadece sınıf dosyasını açın ve yansıma tarayıcısının okumaya izin vermeyerek sınıfın sabit bölümünde sınıf adını bulmak için kontrol edin. (doğrudan) süper sınıfınıza gerekli referansı bile içermeyen sınıflar hakkında daha fazla bilgi Dolaylı olarak daha fazla taramanız gerekir. Şu anda var olan en iyisi bu.
Martin Kersten

Fforw'un cevabı benim için çalıştı ve doğru cevap olarak işaretlenmelidir. Bu açıkça sınıf yolu taraması ile mümkündür.
Farrukh Najmi

Yanılıyorsunuz, Burningwave kütüphanesi ile ilgili aşağıdaki yanıta bakınız

127

Saf Java ile sınıfları taramak kolay değildir.

Yay çerçevesi, ihtiyacınız olanı yapabilen ClassPathScanningCandidateComponentProvider adlı bir sınıf sunar . Aşağıdaki örnek, Sınıfımın tüm alt sınıflarını org.example.package paketinde bulur

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
}

Bu yöntem o olur yani adayları bulmak için bir bayt kodu analiz cihazı kullanılarak ek yarar vardır değil o tarar tüm sınıfları yükleyin.


23
Varsayılan filtreleri devre dışı bırakmak için ClassPathScanningCandidateComponentProvider oluşturulurken parametre olarak False iletilmelidir. Varsayılan filtreler diğer sınıf türleriyle eşleşir, örneğin @Component ile açıklamalı herhangi bir şey. Burada yalnızca AssignableTypeFilter öğesinin etkin olmasını istiyoruz.
MCDS

Kolay olmadığını söylüyorsun, ama saf java ile yapmak istersek nasıl yapardık?
Aequitas

49

Bu, yalnızca yerleşik Java Reflections API'sını kullanarak yapmak mümkün değildir.

Bu bilgiye erişebilmeniz için sınıf yolunuzun gerekli taramasını ve dizine eklemesini yapan bir proje var ...

Yansımalar

Scannotations ruhu içinde bir Java çalışma zamanı meta veri analizi

Yansımalar sınıf yolunuzu tarar, meta verileri dizine ekler, çalışma zamanında sorgulamanıza olanak tanır ve bu bilgileri projenizdeki birçok modül için kaydedip toplayabilir.

Yansımalar'ı kullanarak meta verilerinizi aşağıdakiler için sorgulayabilirsiniz:

  • bir türün tüm alt türlerini al
  • bazı ek açıklamalarla her türe ek açıklama ekleyin
  • ek açıklama parametreleri eşleşmesi de dahil olmak üzere tüm türlere bazı ek açıklamalarla ek açıklama ekleyin
  • tüm yöntemlere bazı açıklamaları ekleyin

(sorumluluk reddi: Kullanmadım, ancak projenin açıklaması ihtiyaçlarınıza tam olarak uyuyor gibi görünüyor.)


1
İlginç. Projenin belgelerinde bahsetmediği bazı bağımlılıklar var gibi görünüyor. Şimdiye kadar (şimdiye kadar bulduğum): javaassist, log4J, XStream
Avrom

3
Bu projekt'i maven ile dahil ettim ve iyi çalıştı. Alt sınıfları almak aslında ilk kaynak kod örneği ve iki satır uzunluğunda :-)
KarlsFriend

Yalnızca yerleşik Java Reflections API'sını kullanmak mümkün değil mi, yoksa bunu yapmak çok zor mu?
Akış

1
Reflections'ı kullanırken dikkatli olun ve ardından uygulama WARF'ı GlassFish'e dağıtın! Guava kütüphanesinde bir çakışma var ve dağıtım CDI dağıtım hatası ile başarısız oluyor: WELD-001408 - daha fazla bilgi için lütfen GLASSFISH-20579'a bakın. FastClasspathScanner bu durumda bir çözümdür.
lu_ko

Sadece bu projeyi deniyorum ve işe yarıyor. Sadece strateji tasarım modelini geliştirmek ve demo sınıfını paylaşacağım tüm strateji sınıfı (alt sınıf) elde etmek için kullanıyorum.
Xin Meng

10

Bir sınıf için oluşturulan Javadoc'un bilinen alt sınıfların (ve arabirimler için bilinen uygulama sınıfları) bir listesini içereceğini unutmayın .


3
bu tamamen yanlıştır, süper sınıf, javadoc veya yorumda bile alt sınıflarına bağlı olmamalıdır.
avcı

@hunter katılmıyorum. JavaDoc'un bilinen alt sınıfların bir listesini içermesi tamamen doğrudur . Tabii ki, "bilinen" aradığınız sınıfı içermeyebilir, ancak bazı kullanım durumları için yeterli olacaktır.
Qw3ry

Ve her durumda bazı sınıfları kaçırabilirsiniz: Sınıf yoluna (çalışma zamanı sırasında) yeni bir kavanoz yükleyebilirim ve daha önce gerçekleşen her algılama başarısız olur.
Qw3ry

10

ClassGraph'ı deneyin . (Feragat, ben yazarım). ClassGraph, belirli bir sınıfın alt sınıflarının taranmasını çalışma zamanında veya derleme zamanında değil, aynı zamanda çok daha fazlasını destekler. ClassGraph, bellekteki tüm sınıf grafiğinin (tüm sınıflar, detaylandırmalar, yöntemler, yöntem parametreleri ve alanlar) soyut bir temsilini oluşturabilir, sınıf yolundaki tüm sınıflar veya beyaz listedeki paketlerdeki sınıflar için, ancak bu sınıf grafiğini sorgulayabilirsiniz. İstediğiniz. ClassGraph, diğer tarayıcılardan daha fazla sınıf yolu spesifikasyon mekanizmasını ve sınıf yükleyiciyi destekler ve ayrıca yeni JPMS modül sistemi ile sorunsuz çalışır, bu nedenle kodunuzu ClassGraph'a dayandırırsanız, kodunuz maksimum taşınabilir olacaktır. API'ya buradan bakın.


9

Bunu birkaç yıl önce yaptım. Bunu yapmanın en güvenilir yolu (yani resmi Java API'leri ve harici bağımlılık olmadan) çalışma zamanında okunabilecek bir liste oluşturmak için özel bir kitapçık yazmaktır.

Komut satırından şu şekilde çalıştırabilirsiniz:

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

veya şöyle karıncadan çalıştırın:

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

Temel kod şöyledir:

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

Basit olması için, komut satırı bağımsız değişkenini ayrıştırmayı kaldırdım ve bir dosya yerine System.out'a yazıyorum.


Bunu programlı olarak kullanmak zor olsa da şunu söylemeliyim ki akıllı bir yaklaşım!
Janaka Bandara

8

Bu partiye birkaç yıl geç kaldığımı biliyorum, ama aynı soruyu çözmeye çalışırken bu soruya rastladım. Bir Eclipse Eklentisi yazıyorsanız (ve böylece önbelleklemelerinden, vb.) Bir arayüz uygulayan sınıfları bulmak için Eclipse'nin dahili aramasını programlı olarak kullanabilirsiniz. İşte benim (çok kaba) ilk kesimim:

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

Şimdiye kadar gördüğüm ilk sorun, sadece tüm alt sınıflarını değil, arayüzü doğrudan uygulayan sınıfları yakalamaktır - ancak küçük bir özyineleme kimseye zarar vermez.


3
VEYA, kendi aramanızı yapmak zorunda olmadığınız anlaşılıyor. : Sen sadece üzerinde .newTypeHierarchy () arayarak ITYPE'ı doğrudan bir ITypeHierarchy alabilirsiniz dev.eclipse.org/newslists/news.eclipse.tools.jdt/msg05036.html
Curtis

7

Diğer cevaplarda belirtilen sınırlamaları göz önünde bulundurarak, openpojo'larıPojoClassFactory ( Maven'de mevcuttur ) aşağıdaki şekilde de kullanabilirsiniz:

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

packageRootArama yapmak istediğiniz paketlerin kök dizesi nerede (örneğin , "com.mycompany"hatta sadece "com") ve Superclasssüper tipiniz (bu arabirimler üzerinde de çalışır).


Şimdiye kadar önerilenlerden en hızlı ve en zarif çözüm.
KidCrippler


4

Özel gereksinimlerinize bağlı olarak, bazı durumlarda Java'nın hizmet yükleyici mekanizması peşinde olduğunuz şeyi başarabilir.

Kısacası, geliştiricilerin bir sınıfın JAR / WAR dosyasının META-INF/servicesdizinindeki bir dosyada listelenerek başka bir sınıfın alt sınıflarını (veya bazı arabirimleri uyguladığını) açıkça bildirmesine izin verir . Daha sonra java.util.ServiceLoader, bir Classnesne verildiğinde , o sınıfın bildirilen tüm alt sınıflarının (ya da Classbir arabirimi temsil ediyorsa, o arabirimi uygulayan tüm sınıfların) örneklerini oluşturacak sınıf kullanılarak keşfedilebilir .

Bu yaklaşımın temel avantajı, tüm sınıfyolunu alt sınıflar için manuel olarak taramaya gerek olmamasıdır - tüm keşif mantığı ServiceLoadersınıf içinde bulunur ve yalnızca META-INF/servicesdizinde açıkça belirtilen sınıfları yükler (sınıf yolundaki her sınıf değil) .

Bununla birlikte, bazı dezavantajlar vardır:

  • Tüm alt sınıfları bulamaz , yalnızca açıkça beyan edilenleri bulur . Bu nedenle, tüm alt sınıfları gerçekten bulmanız gerekiyorsa, bu yaklaşım yetersiz olabilir.
  • Geliştiricinin sınıfı META-INF/servicesdizinin altında açıkça belirtmesini gerektirir . Bu geliştirici üzerinde ek bir yüktür ve hataya açık olabilir.
  • ServiceLoader.iterator()Alt sınıf örneklerini değil onların oluşturduğu Classnesneler. Bu iki soruna neden olur:
    • Alt sınıfların nasıl oluşturulduğu hakkında herhangi bir söz almazsınız - argüman oluşturucu örnekleri oluşturmak için kullanılır.
    • Bu nedenle, alt sınıfların varsayılan bir kurucuya sahip olması ya da açıklamanın arg olmayan bir kurucu bildirmesi gerekir.

Görünüşe göre Java 9 bu eksikliklerin bazılarını (özellikle alt sınıfların somutlaştırılmasıyla ilgili) ele alacaktır.

Bir örnek

Bir arayüz uygulayan sınıflar bulmakla ilgilendiğinizi varsayalım com.example.Example:

package com.example;

public interface Example {
    public String getStr();
}

Sınıf com.example.ExampleImplbu arayüzü uygular:

package com.example;

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

Metni içeren bir dosya oluşturarak sınıfın ExampleImplbir uygulaması olduğunu beyan edersiniz .ExampleMETA-INF/services/com.example.Examplecom.example.ExampleImpl

Ardından, her uygulamasının Examplebir örneğini (bir örneği dahil ExampleImpl) aşağıdaki gibi alabilirsiniz:

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.

3

Bunun elbette sadece mevcut sınıf yolunuzda var olan tüm alt sınıfları bulacağına dikkat edilmelidir. Muhtemelen bu şu anda baktığınız şey için sorun değil ve bunu dikkate aldınız mı, ancak herhangi bir noktada finalvahşi olmayan bir sınıf ( eğer "vahşi" seviyeleri için) serbest bıraktıysanız , o zaman tamamen mümkündür. başkası bilmediğiniz kendi alt sınıfını yazdı.

Bu nedenle, tüm alt sınıfları görmek istemişseniz, çünkü bir değişiklik yapmak istediğiniz ve alt sınıfların davranışlarını nasıl etkilediğini göreceksiniz - o zaman göremediğiniz alt sınıfları aklınızda bulundurun. İdeal olarak tüm özel olmayan yöntemleriniz ve sınıfın kendisi iyi belgelenmelidir; yöntemlerin / özel olmayan alanların anlamlarını değiştirmeden bu belgelere göre değişiklik yapın ve değişikliklerinizin en azından üst sınıf tanımınızı izleyen alt sınıflar için geriye dönük olarak uyumlu olması gerekir.


3

Uygulamanız ve Eclipse arasında bir fark görmenizin nedeni, her seferinde taramanız, Eclipse (ve diğer araçlar) yalnızca bir kez (proje yüklemesi sırasında) çoğu zaman taranması ve bir dizin oluşturmasıdır. Bir dahaki sefere veri istediğinde veri yeniden taranmaz, ancak dizine bakın.


3

Sınıf yolunuzu tüm alt sınıflar için tarayan bir yansıma kütüphanesi kullanıyorum: https://github.com/ronmamo/reflections

Böyle yapılırdı:

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

2

Bunları, üst sınıf yapıcısının (this.getClass (). GetName ()) içindeki statik bir haritaya ekleyin (veya varsayılan bir tane oluşturun), ancak bu çalışma zamanında güncellenecektir. Tembel başlatma bir seçenekse bu yaklaşımı deneyebilirsiniz.



0

Yeni sınıflar koda eklenmiş olup olmadığını görmek için bunu bir test durumda yapmak gerekiyordu. Ben de öyle yaptım

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());
        }
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.