Javaで特定のクラスのすべてのサブクラスをどのように見つけますか?


207

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

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

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


1
ReflectionsとSpringに基づくソリューションは興味深いように見えますが、依存関係のない簡単なソリューションが必要でした。私の元のコード(いくつかの微調整を含む)が進むべき道だったようです。
Avrom、2009

1
確かにgetSupeClassメソッドを再帰的に使用できますか?

特定のクラスのすべてのサブクラスを具体的に探していました。getSuperClassは、クラスがどのサブクラスを持っているかを通知せず、特定のサブクラスの直接のスーパークラスのみを取得します。また、クラスのメソッドisAssignableFromは、提案した内容に適しています(再帰の必要はありません)。
Avrom、2011年

この質問は他の多くの重複からリンクされていますが、有用でプレーンなJavaの回答は含まれていません。ため息...
エリックドゥミニル

回答:


77

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

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


23
これで、org.reflectionsと呼ばれるシンプルなライブラリができ、これと他の一般的なリフレクションタスクに役立ちます。このライブラリを使用すると、reflections.getSubTypesOf(aClazz)) リンクを
Enwired

@matt b-すべてのクラスをスキャンする必要がある場合、少数のクラスだけがクラスをサブクラス化している場合でも、プロジェクトに多くのクラスがあると、パフォーマンスが低下しますか?
LeTex 2015

丁度。すべてのクラスに触れるだけです。独自のスキャナーを定義して、クラスが拡張されないことがわかっている特定のパッケージを除外したり、クラスファイルを開いて、クラスの定数セクションでクラス名を確認したりして、リフレクションスキャナーによる読み取りを回避するなど、スピードアップできます。 (直接)スーパークラスへの必要な参照が含まれていないクラスの詳細情報間接的にさらにスキャンする必要があります。だから現時点で最高です。
Martin Kersten、2016年

fforwの答えは私にとってうまくいき、正解としてマークする必要があります。明らかに、これはクラスパススキャンで可能です。
Farrukh Najmi 2017年

あなたは間違っています。Burningwaveライブラリについての以下の応答を参照してください

127

クラスのスキャンは、純粋な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
}

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


23
ClassPathScanningCandidateComponentProviderを作成してデフォルトのフィルターを無効にする場合は、パラメーターとしてFalseを渡す必要があります。デフォルトのフィルターは、@ Componentで注釈されたものなど、他のタイプのクラスと一致します。ここでは、AssignableTypeFilterをアクティブにするだけです。
MCDS 2013年

簡単ではないとおっしゃっていますが、純粋なJavaで実行したい場合はどうすればよいでしょうか。
Aequitas

49

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

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

反射

Scannotationsの精神によるJavaランタイムメタデータ分析

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

リフレクションを使用すると、次のメタデータをクエリできます。

  • あるタイプのすべてのサブタイプを取得する
  • いくつかの注釈が付いたすべてのタイプを取得する
  • 注釈パラメーターのマッチングを含む、いくつかの注釈で注釈されたすべてのタイプを取得
  • 一部のアノテーションが付けられたすべてのメソッドを取得する

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


1
面白い。プロジェクトには、ドキュメントに記載されていないように見える依存関係がいくつかあるようです。つまり(これまでに見つけたもの):javaassist、log4J、XStream
Avrom

3
このプロジェクトをmavenに含めたところ、問題なく動作しました。サブクラスの取得は実際には最初のソースコードの例であり、2行です:-)
KarlsFriend

組み込みのJava Reflections APIだけを使用することは不可能ですか、それとも非常に不便ですか?
Flow

1
Reflectionsを使用してGlassFishにアプリWARをデプロイするときは注意してください。Guavaライブラリに競合があり、エラーCDIデプロイメントエラーでデプロイメントが失敗します:WELD-001408-詳細については、GLASSFISH-20579を参照してください。FastClasspathScannerはこの場合のソリューションです。
lu_ko 2016年

私はこのプロジェクトを試してみればうまくいきます。私はそれを使用して戦略デザインパターンを強化し、すべての戦略の具象クラス(サブクラス)を取得します。
Xin Meng

10

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


3
これは完全に正しくありません。スーパークラスは、javadocやコメントでも、サブクラスに依存してはなりません。
ハンター

@ハンター同意しない。JavaDocに既知のサブクラスのリストが含まれていることは完全に正しいことです。もちろん、「既知」には探しているクラスが含まれていない場合がありますが、一部のユースケースではそれで十分です。
Qw3ry

そして、あなたはどんな場合でもいくつかのクラスを見逃すかもしれません:私は(実行時に)クラスパスに新しいjarをロードでき、以前に起こったすべての検出は失敗します。
Qw3ry

10

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


9

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

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

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に書き込みます。


これをプログラムで使用するのは難しいかもしれませんが、私は言わなければなりません- スマートなアプローチ!
Janaka Bandara

8

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

これまでに見た最初の問題は、インターフェイスを直接実装するクラスだけをキャッチすることであり、そのすべてのサブクラスをキャッチするのではありません。


3
または、自分で検索する必要がないことがわかります。ITypeから直接.newTypeHierarchy()を呼び出してITypeHierarchyを取得できます:dev.eclipse.org/newslists/news.eclipse.tools.jdt/msg05036.html
Curtis

7

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

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

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


提案されているものよりもはるかに速くて最もエレガントなソリューションです。
KidCrippler 2016年


4

特定の要件によっては、Javaのサービスローダーメカニズムが目的を達成する場合があります。

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

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

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

  • すべてのサブクラスを見つけるのではなく、明示的に宣言されたものだけを見つけます。したがって、本当にすべてのサブクラスを見つける必要がある場合、このアプローチでは不十分な場合があります。
  • 開発者は、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.";
    }
}

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

その後、次のように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

これはもちろん、現在のクラスパスに存在するすべてのサブクラスのみを検出することにも注意してください。おそらくこれは、あなたが現在見ている何のためにOKで、チャンスはあなたがこれを検討したのですが、あなたが任意の時点で非リリースしている場合final(「野生」のさまざまなレベルのために)野生にクラスを、それがその完全に実現可能です他の誰かがあなたが知らない独自のサブクラスを書いた。

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


3

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


3

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

これはどのように行われるかです:

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

2

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



0

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

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());
        }
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.