222

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

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

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

4

18 に答える 18

138

純粋な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
}

この方法には、バイトコードアナライザーを使用して候補を見つけるという追加の利点があります。つまり、スキャンするすべてのクラスが読み込まれるわけではありません。

于 2009-01-30T15:13:56.260 に答える
81

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

Eclipse は、"Display in Type Hierarchy" ボタンを押した時点ですでにすべての型データがロードされているため、"効率的" と思われる時間内にのみスーパークラスとサブクラスについて通知することができます (常にクラスをコンパイルし、クラスパス上のすべてを知っているなど)。

于 2009-01-29T15:58:52.300 に答える
49

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

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

反射

Scannotations の精神に基づく Java ランタイム メタデータ分析

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

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

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

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

于 2009-01-29T16:02:26.537 に答える
13

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

于 2015-09-24T14:55:53.397 に答える
11

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

于 2009-01-29T22:57:11.317 に答える
9

私はこのパーティーに数年遅れていることを知っていますが、同じ問題を解決しようとしてこの質問に出くわしました. 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();
    }
  }

これまでに目にした最初の問題は、すべてのサブクラスではなく、インターフェイスを直接実装するクラスのみをキャッチしていることです。

于 2011-04-20T19:29:56.580 に答える
7

他の回答で言及されている制限を念頭に置いて、次の方法でopenpojoPojoClassFactory ( Maven で利用可能) を使用することもできます。

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

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

于 2013-08-11T18:34:56.513 に答える
3

もちろん、これは現在のクラスパスに存在するすべてのサブクラスのみを見つけることにも注意してください。おそらく、これはあなたが現在見ているものには問題がなく、これを検討した可能性がありますが、任意の時点で非finalクラスを野生に解放した場合 (さまざまなレベルの「野生」)、それは完全に実行可能です他の誰かが、あなたが知らない独自のサブクラスを作成しています。

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

于 2009-01-29T16:31:31.610 に答える
3

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

于 2009-01-29T23:10:50.227 に答える
2

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

于 2015-04-14T04:15:21.300 に答える
1

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

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());
        }
    }
}
于 2018-04-20T16:51:31.237 に答える