Çalışma zamanında, bir Java uygulamasındaki temel sınıfı genişleten tüm sınıfları bulun


147

Böyle bir şey yapmak istiyorum:

List<Animal> animals = new ArrayList<Animal>();

for( Class c: list_of_all_classes_available_to_my_app() )
   if (c is Animal)
      animals.add( new c() );

Bu yüzden, uygulamamın evrenindeki tüm sınıflara bakmak istiyorum ve Animal'den inen bir tane bulduğumda, bu türden yeni bir nesne oluşturmak ve listeye eklemek istiyorum. Bu, bir şeyler listesini güncellemek zorunda kalmadan işlevsellik eklememe izin veriyor. Aşağıdakileri önleyebilirim:

List<Animal> animals = new ArrayList<Animal>();
animals.add( new Dog() );
animals.add( new Cat() );
animals.add( new Donkey() );
...

Yukarıdaki yaklaşımla, sadece Animal'i genişleten yeni bir sınıf oluşturabilirim ve otomatik olarak alınacaktır.

GÜNCELLEME: 10/16/2008 09:00 Pasifik Standart Saati:

Bu soru birçok harika yanıt verdi - teşekkürler. Yanıtlardan ve araştırmamdan, gerçekten yapmak istediğim şeyin Java altında mümkün olmadığını gördüm. Ddimitrov'un ServiceLoader mekanizması gibi çalışabilen yaklaşımlar var - ama istediğim şey için çok ağırlar ve problemi Java kodundan harici bir yapılandırma dosyasına taşıdığımı düşünüyorum. Güncelleme 5/10/19 (11 yıl sonra!) Yardımcı olacak birkaç kütüphaneler şimdi vardır IvanNik en @ göre bu cevabı org.reflections görünüyor iyi. Ayrıca ClassGraph @Luke Hutchison gelen cevap ilginç görünüyor. Cevaplarda da birkaç olasılık daha var.

Ne istediğimi belirtmenin başka bir yolu: Animal sınıfımdaki statik bir işlev, başka bir yapılandırma / kodlama yapmadan Animal'ten miras alan tüm sınıfları bulur ve başlatır. Yapılandırmam gerekirse, onları yine de Animal sınıfında somutlaştırabilirim. Bir Java programı sadece .class dosyalarının gevşek bir federasyon olduğu için anlıyorum.

İlginçtir, bu C # ' da oldukça önemsiz görünüyor .


1
Bir süre aradıktan sonra, Java'da çatlamak zor bir somun gibi görünüyor. İşte bazı bilgiler içeren bir iş parçacığı: forums.sun.com/thread.jspa?threadID=341935&start=15 İçindeki uygulamalar ihtiyacımdan daha fazla, sanırım şimdilik ikinci uygulamaya sadık kalacağım.
JohnnyLambada

bunu bir cevap olarak koy ve okuyucuların oy kullanmasına izin ver
VonC

3
Bunu C # ile yapma bağlantısı özel bir şey göstermez. AC # Assembly, Java JAR dosyasına eşdeğerdir. Derlenmiş sınıf dosyaları (ve kaynakları) topluluğudur. Bağlantı, sınıfların tek bir derlemeden nasıl çıkarılacağını gösterir. Java ile bunu neredeyse kolayca yapabilirsiniz. Sorun sizin için tüm JAR dosyaları ve gerçek gevşek dosyaları (dizinler) bakmak gerekir; aynısını .NET ile yapmanız gerekir (bir çeşit veya çeşitli PATH'de arama yapın).
Kevin Brock

Yanıtlar:


156

Kullandığım org.reflections :

Reflections reflections = new Reflections("com.mycompany");    
Set<Class<? extends MyInterface>> classes = reflections.getSubTypesOf(MyInterface.class);

Başka bir örnek:

public static void main(String[] args) throws IllegalAccessException, InstantiationException {
    Reflections reflections = new Reflections("java.util");
    Set<Class<? extends List>> classes = reflections.getSubTypesOf(java.util.List.class);
    for (Class<? extends List> aClass : classes) {
        System.out.println(aClass.getName());
        if(aClass == ArrayList.class) {
            List list = aClass.newInstance();
            list.add("test");
            System.out.println(list.getClass().getName() + ": " + list.size());
        }
    }
}

6
Org.reflections hakkındaki bağlantı için teşekkürler. Yanlışlıkla bunun .Net dünyasında olduğu kadar kolay olacağını düşündüm ve bu cevap bana çok zaman kazandırdı.
akmad

1
Büyük paket "org.reflections" bu yararlı bağlantı için gerçekten teşekkür ederiz! Bununla nihayet sorunum için uygulanabilir ve temiz bir çözüm buldum.
Hartmut P.7

3
Tüm yansımaları almanın bir yolu var mı, yani belirli bir pakete dikkat etmeden, ancak mevcut tüm sınıfları yüklerken?
Joey Baruch

Sınıfları bulmanın onları örnekleme sorununuzu çözmeyeceğini unutmayın ( animals.add( new c() );kodunuzun 5. satırı ), çünkü Class.newInstance()mevcut olsa da, belirli sınıfın parametresiz bir kurucuya sahip olduğuna dair bir güvenceniz yoktur (C # bunu genel olarak gerektirmenizi sağlar)
usr-local-ΕΨΗΕΛΩΝ

Ayrıca bkz. ClassGraph: github.com/classgraph/classgraph (Feragat, ben yazarım). Ayrı bir cevapta bir kod örneği vereceğim.
Luke Hutchison

34

İstediğinizi yapmanın Java yolu, ServiceLoader mekanizmasını kullanmaktır .

Ayrıca birçok kişi, iyi bilinen bir sınıf yolu konumunda (yani /META-INF/services/myplugin.properties) bir dosyaya sahip olan ve daha sonra bu ada sahip tüm dosyaları tüm kavanozlardan numaralandırmak için ClassLoader.getResources () yöntemini kullanarak kendi dosyalarını döndürür . Bu, her kavanozun kendi sağlayıcılarını dışa aktarmasına izin verir ve Class.forName () öğesini kullanarak yansıma ile başlatabilirsiniz.


1
Sınıflardaki ek açıklamalara dayalı olarak META-INF hizmetler dosyasını kolayca oluşturmak için şunları kontrol edebilirsiniz: metainf-services.kohsuke.org veya code.google.com/p/spi
elek

Bleah. Sadece bir karmaşıklık seviyesi ekler, ancak hem bir sınıf yaratma hem de daha sonra çok uzak bir ülkeye kaydetme ihtiyacını ortadan kaldırmaz.
kevin cline

Lütfen 26 cevap ile aşağıdaki cevaba bakın, çok daha iyi
SobiborTreblinka

4
Gerçekten de, bir çeşit çalışma çözümüne ihtiyacınız varsa, Yansımalar / Taramalar iyi bir seçenektir, ancak her ikisi de dışa bağımlılık gerektirir, belirleyici değildir ve Java Topluluğu Süreci tarafından desteklenmez. Buna ek olarak, herhangi bir zamanda ince havadan yeni bir tane üretmek önemsiz olduğundan, bir arayüzü uygulayan TÜM sınıfları asla güvenilir bir şekilde alamayacağınızı vurgulamak istedim. Tüm siğiller için Java'nın servis yükleyicisini anlamak ve kullanmak kolaydır. Farklı nedenlerle ondan uzak dururdum, ancak sınıf yolu taraması daha da kötü: stackoverflow.com/a/7237152/18187
ddimitrov

11

Bunu en boy odaklı bir bakış açısından düşünün; gerçekten yapmak istediğiniz şey, çalışma süresinde Hayvan sınıfını genişleten tüm sınıfları bilmek. (Sanırım bu, başlığınızdan biraz daha doğru bir açıklama; aksi takdirde, bir çalışma zamanı sorunuz olduğunu sanmıyorum.)

İstediğinizi düşündüğüm, temel sınıfınızın (Hayvan) statik dizinize (ArrayLists'i kendim, ama her birine kendi tercih ediyorum) ekleyen mevcut Sınıfın türünü ekleyen bir kurucu oluşturmaktır.

Yani, kabaca;

public abstract class Animal
    {
    private static ArrayList<Class> instantiatedDerivedTypes;
    public Animal() {
        Class derivedClass = this.getClass();
        if (!instantiatedDerivedClass.contains(derivedClass)) {
            instantiatedDerivedClass.Add(derivedClass);
        }
    }

Tabii ki, instantiatedDerivedClass'ı başlatmak için Animal üzerinde statik bir kurucuya ihtiyacınız olacak ... Bence bu muhtemelen istediğinizi yapacak. Bunun yürütme yoluna bağlı olduğunu unutmayın; Asla çağrılmayan Hayvandan türeyen bir Köpek sınıfınız varsa, bunu Sınıfı listesinde bulunmaz.


6

Ne yazık ki bu tamamen mümkün değildir çünkü ClassLoader size hangi sınıfların mevcut olduğunu söylemeyecektir. Bununla birlikte, böyle bir şey yaparak oldukça yakınlaşabilirsiniz:

for (String classpathEntry : System.getProperty("java.class.path").split(System.getProperty("path.separator"))) {
    if (classpathEntry.endsWith(".jar")) {
        File jar = new File(classpathEntry);

        JarInputStream is = new JarInputStream(new FileInputStream(jar));

        JarEntry entry;
        while( (entry = is.getNextJarEntry()) != null) {
            if(entry.getName().endsWith(".class")) {
                // Class.forName(entry.getName()) and check
                //   for implementation of the interface
            }
        }
    }
}

Düzenleme: johnstok doğru (yorumlarda) bu sadece bağımsız Java uygulamaları için çalışır ve bir uygulama sunucusu altında çalışmaz.


Sınıflar başka yollarla yüklendiğinde, örneğin bir web veya J2EE kapsayıcısında bu işe yaramaz.
johnstok

URL[]ClassLoader hiyerarşisinden yolları (genellikle ancak her zaman mümkün olmayabilir) sorguladıysanız, muhtemelen bir kapsayıcı altında bile daha iyi bir iş yapabilirsiniz . Genellikle SecurityManagerJVM içinde bir yükünüz olacağından izin almanız gerekir .
Kevin Brock

Benim için bir cazibe gibi çalıştı! Bunun sınıf ağırlığındaki tüm sınıflardan geçerek çok ağır ve yavaş olacağından korktum, ama aslında kontrol ettiğim dosyaları "-ejb-" veya diğer tanınabilir dosya adları içerenlerle sınırlandırdım ve hala başlamaktan 100 kat daha hızlı gömülü bir cam balığı veya EJBContainer. Benim özel durumumda, daha sonra "Vatansız", "Durum bilgisi olan" ve "Singleton" ek açıklamalarını arayan sınıfları analiz ettim ve eğer onları görürsem, MockInitialContextFactory'ye JNDI Mapped adını ekledim. Tekrar teşekkürler!
cs94njw

4

Çizgiler Çerçevesi'nden ResolverUtil ( ham kaynak ) kullanabilirsiniz
olan herhangi bir kodu yeniden düzenlemeden basit ve hızlı bir şeye ihtiyacınız varsa .

Sınıflardan herhangi birini yüklememiş basit bir örnek:

package test;

import java.util.Set;
import net.sourceforge.stripes.util.ResolverUtil;

public class BaseClassTest {
    public static void main(String[] args) throws Exception {
        ResolverUtil<Animal> resolver = new ResolverUtil<Animal>();
        resolver.findImplementations(Animal.class, "test");
        Set<Class<? extends Animal>> classes = resolver.getClasses();

        for (Class<? extends Animal> clazz : classes) {
            System.out.println(clazz);
        }
    }
}

class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
class Donkey extends Animal {}

Bu aynı zamanda bir uygulama sunucusunda da çalışır, çünkü çalışmak için tasarlandığı yer budur;)

Kod temel olarak aşağıdakileri yapar:

  • belirttiğiniz paketlerdeki tüm kaynaklar için yineleme
  • yalnızca .class ile biten kaynakları sakla
  • Bu sınıfları kullanarak ClassLoader#loadClass(String fullyQualifiedName)
  • Kontrol eder Animal.class.isAssignableFrom(loadedClass);

4

Belirli bir sınıfın tüm alt sınıflarını listelemek için en sağlam mekanizma şu anda ClassGraph'dır , çünkü yeni JPMS modül sistemi de dahil olmak üzere mümkün olan en geniş sınıf yolu belirtim mekanizması dizisini işler . (Ben yazarım.)

List<Class<Animal>> animals;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("com.zoo.animals")
        .enableClassInfo().scan()) {
    animals = scanResult
        .getSubclasses(Animal.class.getName())
        .loadClasses(Animal.class);
}

Sonuç List <Class <Animal>>
türündedir

2

Java, sınıfları dinamik olarak yükler, böylece sınıf evreniniz yalnızca önceden yüklenmiş (ve henüz yüklenmemiş) sınıflar olacaktır. Belki de her yüklü sınıfın üst tiplerini kontrol edebilecek özel bir sınıf yükleyici ile bir şeyler yapabilirsiniz. Yüklü sınıflar kümesini sorgulamak için bir API olduğunu sanmıyorum.


2

bunu kullan

public static Set<Class> getExtendedClasses(Class superClass)
{
    try
    {
        ResolverUtil resolver = new ResolverUtil();
        resolver.findImplementations(superClass, superClass.getPackage().getName());
        return resolver.getClasses();  
    }
    catch(Exception e)
    {Log.d("Log:", " Err: getExtendedClasses() ");}

    return null;
}

getExtendedClasses(Animals.class);

Düzenle:

  • için kütüphane (ResolverUtil): Stripes

2
ResolverUtil hakkında biraz daha konuşmanız gerekecek. Bu ne? Hangi kütüphaneden geliyor?
JohnnyLambada

1

Bu soruyu cevaplayan herkese teşekkürler.

Bu gerçekten çatlamak için zor bir somun gibi görünüyor. Ben vazgeçti ve benim taban sınıfta statik bir dizi ve getter oluşturma sona erdi.

public abstract class Animal{
    private static Animal[] animals= null;
    public static Animal[] getAnimals(){
        if (animals==null){
            animals = new Animal[]{
                new Dog(),
                new Cat(),
                new Lion()
            };
        }
        return animals;
    }
}

Görünüşe göre Java sadece C # gibi kendini keşfedebilirlik için ayarlanmadı. Bir Java uygulaması sadece bir yerde bir dizin / kavanoz dosyasında .class dosyaları bir koleksiyon olduğundan, çalışma zamanı referans verilene kadar bir sınıf hakkında bilmiyor sorun olduğunu varsayalım. O zaman yükleyici yükler - Ben yapmaya çalışıyorum ne ben başvurmadan önce keşfetmek hangi dosya sistemine çıkmadan ve bakmadan mümkün değildir keşfetmek.

Her zaman kendimden bahsetmek yerine kendimi keşfedebilecek kodu severim, ama ne yazık ki bu da işe yarıyor.

Tekrar teşekkürler!


1
Java kendi kendini keşfetme için ayarlanmıştır. Sadece biraz daha iş yapman gerekiyor. Ayrıntılar için cevabıma bakın.
Dave Jarvis

1

OpenPojo kullanarak aşağıdakileri yapabilirsiniz:

String package = "com.mycompany";
List<Animal> animals = new ArrayList<Animal>();

for(PojoClass pojoClass : PojoClassFactory.enumerateClassesByExtendingType(package, Animal.class, null) {
  animals.add((Animal) InstanceFactory.getInstance(pojoClass));
}

0

Bu zor bir sorundur ve bu bilgiyi statik analiz kullanarak bulmanız gerekecektir, çalışma zamanında kolayca bulunmaz. Temel olarak uygulamanızın sınıfyolunu alın ve kullanılabilir sınıfları tarayın ve kalıtım yoluyla aldığı sınıfın bayt kodu bilgilerini okuyun. Bir sınıf köpeğin doğrudan hayvandan miras kalmayabileceğini, ancak hayvandan miras alan evcil hayvandan miras alabileceğini unutmayın, bu yüzden bu hiyerarşiyi takip etmeniz gerekecektir.


0

Bunun bir yolu, sınıfların statik bir başlatıcı kullanmasını sağlamaktır ... Bunların kalıtsal olduğunu sanmıyorum (eğer varsa işe yaramaz):

public class Dog extends Animal{

static
{
   Animal a = new Dog();
   //add a to the List
}

Bu kodu ilgili tüm sınıflara eklemenizi gerektirir. Ancak, bir yerde büyük bir çirkin döngüden kaçınır, Hayvanların çocuklarını arayan her sınıfı test eder.


3
Benim durumumda bu işe yaramıyor. Sınıfa hiçbir yerde başvurulmadığından statik yükleyici asla çağrılmaz. Sınıf yüklendiğinde her şeyden önce statik bir başlatıcı çağrılıyor gibi görünüyor - ama benim durumumda sınıf asla yüklenmiyor.
JohnnyLambada

0

Bu sorunu Paket Düzeyi Ek Açıklamaları kullanarak oldukça zarif bir şekilde çözdüm ve daha sonra bu ek açıklamanın argüman olarak bir sınıf listesi olmasını sağladım.

Arabirim uygulayan Java sınıflarını bulma

Uygulamalar sadece bir package-info.java oluşturmak ve sihirli ek açıklamayı desteklemek istedikleri sınıfların listesine koymak zorundadır.

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.