web-dev-qa-db-ja.com

Javaで特定のクラスのすべてのサブクラスを見つけるにはどうすればよいですか?

どのようにJavaの特定のクラスのすべてのサブクラス(または特定のインターフェイスのすべての実装者)を見つけようとするのですか?今のところ、これを行う方法はありますが、非常に非効率的です(控えめに言っても)。メソッドは次のとおりです。

  1. クラスパスに存在するすべてのクラス名のリストを取得します
  2. 各クラスをロードし、目的のクラスまたはインターフェースのサブクラスまたは実装者であるかどうかを確認するためにテストします

Eclipseには、これを非常に効率的に表示できるタイプ階層と呼ばれる素晴らしい機能があります。どうやってプログラムで実行しますか?

190
Avrom

説明した以外の方法はありません。それについて考えてみてください-クラスパス上の各クラスをスキャンせずに、どのクラスがClassXを拡張しているのかを誰がどのように知ることができますか?

Eclipseは、「タイプ階層で表示」ボタンを押した時点ですべてのタイプデータがすでにロードされているため、「効率的な」時間のように見えるスーパークラスとサブクラスについてのみ通知できます(常にクラスをコンパイルし、クラスパス上のすべてのことを知っているなど)。

69
matt b

Pure Javaでは、クラスのスキャンは簡単ではありません。

Springフレームワークは、必要なことを実行できる ClassPathScanningCandidateComponentProvider というクラスを提供します。次の例では、パッケージorg.example.package内のMyClassのすべてのサブクラスを検索します

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
}

このメソッドには、バイトコードアナライザーを使用して候補を見つけるという追加の利点があります。つまり、スキャンするすべてのクラスをnotロードします。

120
fforw

これは、組み込みのJava Reflections APIのみを使用して行うことはできません。

クラスパスの必要なスキャンとインデックス作成を行うプロジェクトが存在するため、この情報にアクセスできます...

反射

Scannotationsの精神でのJavaランタイムメタデータ分析

Reflectionsは、クラスパスをスキャンし、メタデータにインデックスを付け、実行時にクエリを実行できるようにし、プロジェクト内の多くのモジュールの情報を保存および収集できます。

Reflectionsを使用すると、次のメタデータを照会できます。

  • あるタイプのすべてのサブタイプを取得する
  • 何らかの注釈が付けられたすべてのタイプを取得する
  • 一致する注釈パラメーターを含む、何らかの注釈が付けられたすべてのタイプを取得します
  • いくつかの注釈が付いたすべてのメソッドを取得します

(免責事項:私はそれを使用していませんが、プロジェクトの説明はあなたのニーズにぴったり合っているようです。)

47
Mark Renouf

クラス用に生成された Javadoc には、既知のサブクラスのリスト(およびインターフェースの場合は既知の実装クラス)が含まれることを忘れないでください。

10
Rob

数年前にこれをやった。これを行うための最も信頼できる方法(つまり、公式のJava AP​​Iを使用し、外部依存関係がない場合)は、カスタムドックレットを作成して、実行時に読み取れるリストを作成することです。

次のようにコマンドラインから実行できます:

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

または、次のようにantから実行します。

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

基本的なコードは次のとおりです。

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

簡単にするために、コマンドライン引数の解析を削除し、ファイルではなくSystem.outに書き込みます。

8
David Leppik

他の回答に記載されている制限を念頭に置いて、次の方法で openpojoのPojoClassFactoryMavenで利用可能 )を使用することもできます。

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

ここで、packageRootは検索するパッケージのルート文字列(例:"com.mycompany"または"com")であり、Superclassはスーパータイプです(これはインターフェースで機能しますまあ)。

7
mikołak

私はこのパーティーに数年遅れていることを知っていますが、同じ問題を解決しようとしてこの質問に出会いました。 Eclipseプラグインを作成している(したがって、キャッシュなどを利用している)場合は、インターフェイスを実装するクラスを見つけるために、Eclipseの内部検索をプログラムで使用できます。これが私の(非常にラフな)最初のカットです:

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

私がこれまでに見た最初の問題は、すべてのサブクラスではなく、インターフェイスを直接実装するクラスだけをキャッチしていることです。しかし、少しの再帰は誰も傷つけません。

7
Curtis

ClassGraph を試してください。 (免責事項、私は著者です)。 ClassGraphは、実行時またはビルド時のいずれかで、指定されたクラスのサブクラスのスキャンをサポートしますが、さらに多くのスキャンもサポートします。 ClassGraphは、メモリ内、クラスパス上のすべてのクラス、またはホワイトリストに登録されたパッケージ内のクラスについて、クラスグラフ全体(すべてのクラス、注釈、メソッド、メソッドパラメーター、フィールド)の抽象表現を構築できますが、そのクラスグラフをクエリできますあなたが欲しい。 ClassGraphは、他のスキャナーよりも クラスパス指定メカニズムとクラスローダー をサポートしており、新しいJPMSモジュールシステムともシームレスに動作します。そのため、ClassGraphに基づいてコードを作成すると、コードの移植性が最大限になります。 ここのAPIを参照してください

6
Luke Hutchison

実装とEclipseの違いがわかるのは、毎回スキャンするのに対し、Eclipse(および他のツール)は1回だけスキャンし(ほとんどの場合、プロジェクトのロード中)、インデックスを作成するためです。次回データを要求するとき、再度スキャンすることはありませんが、インデックスを見てください。

3
OscarRyz

同様に、現在のクラスパスに存在するすべてのサブクラスのみが検索されることにも注意してください。おそらくこれはあなたが現在見ているものには問題ありません。おそらくこれを検討した可能性がありますが、ある時点で非finalクラスを(さまざまなレベルの「野生」に対して)リリースした場合他の誰かがあなたが知らない自分のサブクラスを書いていることは完全に実現可能です。

したがって、変更を加えたいためにすべてのサブクラスを表示したい場合、サブクラスの動作にどのように影響するかを確認する場合は、表示できないサブクラスを念頭に置いてください。理想的には、すべての非プライベートメソッド、およびクラス自体を十分に文書化する必要があります。メソッド/非プライベートフィールドのセマンティクスを変更せずに、このドキュメントに従って変更を加えてください。少なくともスーパークラスの定義に従ったサブクラスについては、変更には下位互換性が必要です。

3
Andrzej Doyle

org.reflections.Reflectionsを使用して抽象クラスのサブクラスを取得する簡単なデモを作成します。

https://github.com/xmeng1/ReflectionsDemo

3
Xin Meng

特定の要件に応じて、Javaの service loader メカニズムが目的を達成する場合があります。

つまり、開発者は、JAR/WARファイルのMETA-INF/servicesディレクトリ内のファイルにリストすることにより、クラスが他のクラスをサブクラス化する(またはインターフェイスを実装する)ことを明示的に宣言できます。その後、 Java.util.ServiceLoader クラスを使用して検出できます。このクラスは、Classオブジェクトを指定すると、そのクラスの宣言されたすべてのサブクラスのインスタンスを生成します(または、Classがインターフェイスを表す場合、それを実装するすべてのクラスインタフェース)。

このアプローチの主な利点は、サブクラスのクラスパス全体を手動でスキャンする必要がないことです。すべての検出ロジックはServiceLoaderクラス内に含まれ、META-INF/servicesディレクトリで明示的に宣言されたクラスのみをロードしますクラスパス)。

ただし、いくつかの欠点があります。

  • allサブクラスは検出されず、明示的に宣言されたサブクラスのみが検出されます。そのため、すべてのサブクラスを本当に見つける必要がある場合、このアプローチでは不十分な場合があります。
  • 開発者は、META-INF/servicesディレクトリの下でクラスを明示的に宣言する必要があります。これは開発者にとって追加の負担であり、エラーが発生しやすくなります。
  • ServiceLoader.iterator()は、Classオブジェクトではなく、サブクラスインスタンスを生成します。これにより、2つの問題が発生します。
    • サブクラスがどのように構築されるかについては何も言えません-引数なしのコンストラクターがインスタンスの作成に使用されます。
    • そのため、サブクラスにはデフォルトのコンストラクターが必要です。または、引数なしのコンストラクターを明示的に宣言する必要があります。

どうやらJava 9はこれらの欠点(特に、サブクラスのインスタンス化に関する欠点)のいくつかに対処する予定です。

インターフェイスcom.example.Exampleを実装するクラスを見つけることに興味があるとします:

package com.example;

public interface Example {
    public String getStr();
}

クラスcom.example.ExampleImplはそのインターフェースを実装します:

package com.example;

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

テキストMETA-INF/services/com.example.Exampleを含むファイルcom.example.ExampleImplを作成することにより、クラスExampleImplExampleの実装であることを宣言します。

その後、次のようにExampleの各実装のインスタンス(ExampleImplのインスタンスを含む)を取得できます。

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
Mac

私はすべてのサブクラスのクラスパスをスキャンするリフレクションライブラリを使用しています: https://github.com/ronmamo/reflections

これはそれが行われる方法です:

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

それらを親クラスコンストラクター内の静的マップ(this.getClass()。getName())に追加します(またはデフォルトコンストラクターを作成します)が、これは実行時に更新されます。遅延初期化がオプションの場合、このアプローチを試すことができます。

2

新しいクラスがコードに追加されたかどうかを確認するために、テストケースとしてこれを行う必要がありました。これは私がやったことです

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