it-swarm.dev

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

Java'da belirli bir sınıfın (veya belirli bir arayüzün tüm uygulayıcılarının) tüm alt sınıflarını nasıl bulabilir ve bulmaya çalışabilirim? en azını söyle) . Yöntem: 

  1. Sınıf yolunda bulunan 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 arabirimin alt sınıfı veya uygulayıcısı olup olmadığını görmek için test edin

Eclipse'de, bunu oldukça verimli bir şekilde göstermeyi başaran Tip Hiyerarşisi adı verilen Güzel bir özellik var.

181
Avrom

Bunu anlattıklarınızdan başka yapmanın yolu yok. Bir düşünün - nasıl bir sınıfın sınıf yolunda her sınıfı taramadan ClassX'i genişlettiğini nasıl bilebilir?

Eclipse, yalnızca "verimli" bir zaman diliminde görünen süper ve alt sınıfları size anlatabilir çünkü zaten "Tip Hiyerarşisinde Göster" düğmesine bastığınız noktada yüklü olan tüm tür verilere sahiptir (çünkü sınıflarınızı sürekli derler, sınıf yolu üzerindeki her şeyi bilir, vb.).

68
matt b

Yalnızca Java ile sınıf taraması yapmak kolay değildir. 

Yaylı çerçeve, ClassPathScanningCandidateComponentProvider adlı bir sınıfı, ihtiyacınız olanı yapabilecek şekilde sunar. Aşağıdaki örnek, MyClass'ı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, adayları bulmak için bir bytecode analizörü kullanmanın ek yararına sahiptir, yani taradığı tüm sınıfları değil yükleyecektir.

114
fforw

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

Sınıf yolunuzda gerekli taramayı ve indekslemeyi yapan bir proje var, böylece bu bilgilere erişebilirsiniz ...

Yansımalar

Scannotations'ın ruhuna dayanan bir Java çalışma zamanı meta veri analizi

Reflections sınıf yolunuzu tarar, meta verileri indeksler, çalışma zamanında sorgulamanıza izin verir ve projenizdeki birçok modül için bu bilgileri kaydedebilir ve toplayabilir.

Yansımaları kullanarak meta verilerinizi sorgulayabilirsiniz:

  • bir tür alt türü al
  • her türlü ek not ile açıklamalı
  • eşleştirme ek açıklama parametreleri de dahil olmak üzere, ek ek açıklamalarla birlikte tüm türleri ek açıklama alın
  • bazı yöntemleri açıklamalı olarak almak

(feragatname: Kullanmadım, ancak projenin açıklaması ihtiyaçlarınız için tam olarak uygun görünüyor.)

42
Mark Renouf

Bir sınıf için üretilen Javadoc 'in bilinen alt sınıfların bir listesini içereceğini unutmayın (ve arayüzler için, bilinen uygulama sınıflarının).

10
Rob

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

Bunu aşağıdaki gibi komut satırından çalıştırabilirsiniz:

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

ya da karıncadan şöyle kaç:

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

İşte temel 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;
        }
    }
}

Basit olması için, komut satırı argümanının ayrıştırılmasını kaldırdım ve bir dosya yerine System.out'a yazıyorum.

8
David Leppik

Bu partiye birkaç yıl geç kaldığımı biliyorum ama aynı sorunu çözmeye çalışırken bu soruyla karşılaştım. Bir Eclipse Eklentisi yazıyorsanız (ve bu nedenle önbelleklerinden, vb. Faydalanırsanız) Eclipse'in dahili aramasını programlı olarak kullanabilirsiniz, böylece bir arayüz uygulayan sınıfları bulabilirsiniz. İşte benim (çok kaba) ilk kesim:

  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, aynı zamanda doğrudan arayüzü uygulayan sınıfları yakalamam - ama biraz tekrarlama hiç kimseye zarar vermem.

7
Curtis

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

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

Burada packageRoot, arama yapmak istediğiniz paketlerin köküdür (örneğin, "com.mycompany" veya hatta sadece "com") ve Superclass sizin süper tipinizdir (bu, arayüzlerde de çalışır).

6
mikołak

ClassGraph öğesini deneyin. (Feragatname, ben yazarım). ClassGraph, belirli bir sınıfın alt sınıfları için çalışma zamanında veya oluşturma zamanında değil, aynı zamanda çok daha fazla taramayı da destekler. ClassGraph, tüm sınıf grafiğinin (tüm sınıflar, ek açıklamalar, yöntemler, yöntem parametreleri ve alanlar) bellekte, sınıf yolundaki tüm sınıflar için veya beyaz listedeki paketlerdeki sınıflar için soyut bir sunum oluşturabilir ve bu sınıf grafiğini sorgulayabilirsiniz. İstediğiniz. ClassGraph, diğer tarayıcılardan daha fazla daha fazla sınıf yolu özellik mekanizmasını ve sınıf yükleyicisini destekler ve ayrıca yeni JPMS modül sistemiyle de sorunsuz çalışır, bu nedenle kodunuzu ClassGraph'a dayandırırsanız, kodunuz maksimum taşınabilir olur. API'yi burada görün.

6
Luke Hutchison

Uygulamanız ve Eclipse arasında bir fark görmenizin nedeni, her defasında taramanız, Eclipse (ve diğer araçlar) yalnızca bir kez taramaktır (çoğu zaman proje yüklemesi sırasında) ve bir dizin oluşturur. Verileri bir dahaki sefere sorduğunuzda tekrar taramaz, ancak dizine bakın.

3
OscarRyz

Tabii ki bunun sadece mevcut sınıf yolunuzda bulunan tüm alt sınıfları bulacağı da belirtilmelidir. Muhtemelen şu anda bakmakta olduğunuz şey için sorun yok ve olasılıkları bunu göz önünde bulundurmuş olabilirsiniz, ancak herhangi bir noktada vahşi (_ "vahşi" seviyeleri için) vahşi bir final sınıfı bırakmamışsanız bilmeyeceğiniz bir başkasının kendi alt sınıfını yazması tamamen uygundur.

Bu nedenle, eğer tüm alt sınıfları görmek istemeniz durumunda, bir değişiklik yapmak istediğinizde ve bunun alt sınıfların davranışı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öntemlerin ve sınıfın kendisinin iyi belgelenmesi gerekir; yöntemlerin/özel olmayan alanların anlamlarını değiştirmeden bu dokümantasyona göre değişiklikler yapın ve değişikliklerin 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
Andrzej Doyle

Ben sadece soyut sınıfın alt sınıflarını almak için org.reflections.Reflections kullanmak için basit bir demo yazarım:

https://github.com/xmeng1/ReflectionsDemo

3
Xin Meng

Sınıf yolunu tüm alt sınıflara tarayan bir yansıma kütüphanesi kullanıyorum: https://github.com/ronmamo/reflections

Bu nasıl yapılırdı:

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

Özel gereksinimlerinize bağlı olarak, bazı durumlarda Java servis yükleyicisi mekanizması peşinde olduğunuz şeyi başarabilir.

Kısacası, geliştiricilerin açıkça bir sınıfın, JAR/WAR dosyasının META-INF/services dizinindeki bir dosyada listeleyerek başka bir sınıfı (veya bazı arayüzleri uyguladığı) alt sınıflara aldığını açıklamasını sağlar. Daha sonra, Java.util.ServiceLoader / sınıfı kullanılarak, bir Class nesnesi verildiğinde, o sınıfın bildirilen tüm alt sınıflarının örneklerini üretecek (veya Class bir arabirimi temsil ediyorsa, bu arabirimi uygulayan tüm sınıfları) kullanarak keşfedilebilir.

Bu yaklaşımın en büyük avantajı, tüm sınıf yolunu alt sınıflar için manuel olarak taramaya gerek olmamasıdır - tüm keşif mantığı ServiceLoader sınıfının içinde bulunur ve yalnızca META-INF/services dizininde açıkça belirtilen sınıfları yükler (yalnızca sınıftaki her sınıf değil) sınıf yolu).

Bununla birlikte, bazı dezavantajlar da vardır:

  • All alt sınıflarını bulamaz, yalnızca açıkça bildirilenleri bulamaz. Dolayısıyla, tüm alt sınıfları gerçekten bulmanız gerekiyorsa, bu yaklaşım yetersiz olabilir.
  • Geliştiricinin, META-INF/services dizini altındaki sınıfı açıkça bildirmesini gerektirir. Bu, geliştirici için ek bir yüktür ve hataya açık olabilir.
  • ServiceLoader.iterator(), Class nesnelerini değil, alt sınıf örneklerini oluşturur. Bu iki soruna neden olur:
    • Alt sınıfların nasıl yapıldığına dair hiçbir şey söylemezsiniz - no-arg yapıcısı örnekleri oluşturmak için kullanılır.
    • Bu nedenle, alt sınıfların varsayılan bir kurucuya sahip olması ya da açıkça bir argüman olmayan kurucu bildirmesi gerekir.

Görünüşe göre Java 9 bu eksikliklerin bazılarına hitap ediyor olacak (özellikle alt sınıfların başlatılmasıyla ilgili olanlar).

Bir örnek

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

package com.example;

public interface Example {
    public String getStr();
}

com.example.ExampleImpl sınıfı şu arabirimi uygular:

package com.example;

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

ExampleImpl sınıfı, META-INF/services/com.example.Example metnini içeren bir com.example.ExampleImpl dosyası oluşturarak Example uygulamasının bir uygulamasıdır.

Ardından, Example uygulamasının her bir örneğini (ExampleImpl örneği dahil) aşağıdaki şekilde elde edebilirsiniz:

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

Üst sınıf yapıcısının içindeki (this.getClass (). GetName ()) statik bir haritaya ekleyin (veya varsayılan bir tane oluşturun) ancak bu çalışma zamanında güncellenir. Eğer tembel başlatma bir seçenek ise, bu yaklaşımı deneyebilirsiniz.

1

Bunu, bir sınama örneği olarak, koda yeni sınıfların eklenip eklenmediğini görmek için yapmam 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());
        }
    }
}
0
Cutetare