web-dev-qa-db-ja.com

実行時に、ベースクラスを拡張するJavaアプリケーション内のすべてのクラスを見つける

私はこのようなことをしたい:

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

そのため、アプリケーションのユニバースにあるすべてのクラスを確認し、Animalから派生したクラスを見つけたら、そのタイプの新しいオブジェクトを作成してリストに追加します。これにより、物事のリストを更新することなく機能を追加できます。私は次を避けることができます:

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

上記のアプローチを使用すると、Animalを拡張する新しいクラスを作成するだけで、自動的に取得されます。

更新:2008年10月16日午前9時(太平洋標準時):

この質問は多くの素晴らしい回答を生み出しました-ありがとう。 回答と私の研究から、私が本当にやりたいことは、Javaでは不可能であることがわかりました。動作するddimitrovのServiceLoaderメカニズムなどのアプローチがありますが、私が望むものには非常に重く、単に問題をJavaコードから外部の構成ファイルに移動すると信じています。 5/10/19更新(11年後!)@IvanNikの answerorg.reflections はよさそうです。また、@ Luke Hutchisonの answerClassGraph も興味深いようです。答えにはさらにいくつかの可能性があります。

私が望むものを述べる別の方法:Animalクラスの静的関数は、Animalを継承するすべてのクラスを見つけてインスタンス化します-それ以上の構成/コーディングはありません。構成する必要がある場合は、とにかくAnimalクラスでインスタンス化することもできます。 Javaプログラムは.classファイルの単なるゆるい連合であり、それがまさにそうだということを理解しています。

興味深いことに、これはC#では かなり些細なことです

137
JohnnyLambada

org.reflections を使用します。

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

もう一つの例:

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

Javaの目的は、 ServiceLoader メカニズムを使用することです。

また、多くの人々は、よく知られたクラスパスの場所(つまり、/ META-INF/services/myplugin.properties)にファイルを置き、 ClassLoader.getResources() を使用してすべてのファイルを列挙することにより、すべてのjarからの名前。これにより、各jarが独自のプロバイダーをエクスポートでき、Class.forName()を使用してリフレクションによってインスタンス化できます。

34
ddimitrov

これについては、アスペクト指向の観点から考えてください。本当にやりたいのは、HAVEがAnimalクラスを拡張した実行時のすべてのクラスを知っていることです。 (これはタイトルよりも問題の正確な説明だと思います。それ以外の場合、実行時の質問はないと思います。)

したがって、あなたが欲しいと思うのは、静的クラス(私はArrayListsを好みますが、それぞれに)インスタンス化されている現在のクラスの型を追加する基本クラス(Animal)のコンストラクタを作成することです。

だから、大体;

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

もちろん、animalの静的コンストラクターを使用して、instantiatedDerivedClassを初期化する必要があります。これは実行パスに依存することに注意してください。呼び出されることのないAnimalから派生したDogクラスがある場合、Animal Classリストには含まれません。

8
Paul Sonier

残念ながら、ClassLoaderは使用可能なクラスを通知しないため、これは完全には不可能です。ただし、次のようなことを行うことで、かなり接近することができます。

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

編集: johnstokは(コメントで)正しく、これはスタンドアロンJavaアプリケーションでのみ機能し、アプリケーションサーバーでは機能しません。

6

ResolverUtilraw source )を使用できます Stripes Framework
既存のコードをリファクタリングせずに、シンプルで迅速な何かが必要な場合。

クラスをロードしていない単純な例を次に示します。

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

これは、アプリケーションサーバーでも機能するように設計されているためです。

コードは基本的に次のことを行います。

  • 指定したパッケージ内のすべてのリソースを反復処理します
  • .classで終わるリソースのみを保持する
  • ClassLoader#loadClass(String fullyQualifiedName)を使用してこれらのクラスをロードします
  • Animal.class.isAssignableFrom(loadedClass);かどうかを確認します
4
DJDaveMark

ClassGraph を試してください。 (免責事項、私は著者です。)指定された例の場合、ClassGraphコードは次のようになります。

List<Class<Animal>> animals =
    new ClassGraph()
        .whitelistPackages("com.Zoo.animals")
        .enableAllInfo()
        .scan()
        .getSubclasses(Animal.class.getName())
        .loadClasses(Animal.class);

ClassGraphはこれ以上のことができます- APIをチェックしてください

4
Luke Hutchison

Javaはクラスを動的にロードするため、クラスのユニバースは、すでにロードされている(まだアンロードされていない)クラスのみになります。おそらく、ロードされた各クラスのスーパータイプをチェックできるカスタムクラスローダーで何かを行うことができます。ロードされたクラスのセットを照会するAPIはないと思います。

2
thoroughly

これを使って

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

編集:

  • (ResolverUtil)のライブラリ: Stripes
2
Ali Bagheri

この質問に答えてくれたすべての人に感謝します。

これは確かにクラックするのが難しいナットのようです。私はベースクラスで静的配列とゲッターをあきらめて作成しました。

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

Javaは、C#のように自己発見のために設定されていないようです。問題は、Javaアプリがディレクトリ/ jarファイルのどこかにある.classファイルの単なるコレクションであるため、参照されるまでランタイムがクラスを認識しないことだと思います。その時点でローダーはそれをロードします-私がやろうとしているのは、それを参照する前にそれを発見することです。

私は自分自身について話す必要がなく、自分自身を発見できるコードが常に好きですが、残念ながらこれも機能します。

再度、感謝します!

1
JohnnyLambada

OpenPojo を使用すると、次のことができます。

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));
}
1
Osman Shoukry

1つの方法は、クラスに静的初期化子を使用させることです...これらは継承されていないと思います(そうだと動作しません):

public class Dog extends Animal{

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

関係するすべてのクラスにこのコードを追加する必要があります。しかし、どこかで大きなloopいループが発生することを避け、Animalの子を検索するすべてのクラスをテストします。

0
patros

これは難しい問題であり、静的分析を使用してこの情報を見つける必要があります。実行時には簡単に入手できません。基本的にアプリのクラスパスを取得し、使用可能なクラスをスキャンして、継承元のクラスのバイトコード情報を読み取ります。クラスDogはAnimalから直接継承することはできませんが、Animalから継承するPetから継承する可能性があるため、その階層を追跡する必要があることに注意してください。

0
pdeva

パッケージレベルのアノテーションを使用してこの問題をかなりエレガントに解決し、そのアノテーションに引数としてクラスのリストを持たせました。

インターフェイスを実装するJavaクラスを見つける

実装は、package-info.Javaを作成し、サポートしたいクラスのリストにマジックアノテーションを入れるだけです。

0
Adam Gent