160

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

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:00 太平洋標準時:

この質問に対して多くの素晴らしい回答が寄せられました -- ありがとうございます。 回答と私の調査から、私が本当にやりたいことは Java では不可能であることがわかりました。機能する ddimitrov の ServiceLoader メカニズムなどのアプローチがありますが、それらは私が望むものに対して非常に重く、Java コードから外部構成ファイルに問題を移すだけだと思います。 2019 年 5 月 10 日更新(11 年後!) @IvanNik の回答 org.reflectionsによると、これに役立つライブラリがいくつかあります。@Luke Hutchisonの答えからのClassGraphも面白そうです。答えにはさらにいくつかの可能性があります。

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

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

4

14 に答える 14

171

私は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());
        }
    }
}
于 2012-02-11T14:29:32.627 に答える
36

必要なことを Java で行う方法は、ServiceLoaderメカニズムを使用することです。

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

于 2008-10-15T23:38:31.910 に答える
10

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

したがって、インスタンス化されている現在のクラスのタイプを静的配列に追加する基本クラス(Animal)のコンストラクターを作成する必要があると思います(私はArrayListsを好みますが、それぞれ独自のものにします)。

だから、大まかに;

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

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

于 2008-10-15T21:07:09.577 に答える
8

残念ながら、これは完全には不可能です。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アプリケーションでのみ機能し、アプリケーションサーバーでは機能しません。

于 2008-10-15T18:41:04.693 に答える
2

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

于 2008-10-15T17:55:20.387 に答える
1

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

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

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 ファイルの単なるコレクションであるため、参照されるまでランタイムがクラスについて認識しないことだと思います。その時点で、ローダーはそれをロードします。私がやろうとしているのは、参照する前にそれを発見することです。これは、ファイル システムに出て調べなければ不可能です。

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

再度、感謝します!

于 2008-10-15T19:04:01.917 に答える
0

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

于 2008-10-15T18:52:00.753 に答える
0

1 つの方法は、クラスで静的イニシャライザを使用することです...これらは継承されているとは思いません (継承されている場合は機能しません)。

public class Dog extends Animal{

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

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

于 2008-10-15T19:29:37.870 に答える
0

パッケージ レベルの注釈を使用してこの問題を非常にエレガントに解決し、その注釈に引数としてクラスのリストを持たせました。

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

実装は、package-info.java を作成し、サポートしたいクラスのリストとともに魔法の注釈を入れるだけです。

于 2011-04-29T12:15:12.787 に答える