どのようにJavaの特定のクラスのすべてのサブクラス(または特定のインターフェイスのすべての実装者)を見つけようとするのですか?今のところ、これを行う方法はありますが、非常に非効率的です(控えめに言っても)。メソッドは次のとおりです。
Eclipseには、これを非常に効率的に表示できるタイプ階層と呼ばれる素晴らしい機能があります。どうやってプログラムで実行しますか?
説明した以外の方法はありません。それについて考えてみてください-クラスパス上の各クラスをスキャンせずに、どのクラスがClassXを拡張しているのかを誰がどのように知ることができますか?
Eclipseは、「タイプ階層で表示」ボタンを押した時点ですべてのタイプデータがすでにロードされているため、「効率的な」時間のように見えるスーパークラスとサブクラスについてのみ通知できます(常にクラスをコンパイルし、クラスパス上のすべてのことを知っているなど)。
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ロードします。
これは、組み込みのJava Reflections APIのみを使用して行うことはできません。
クラスパスの必要なスキャンとインデックス作成を行うプロジェクトが存在するため、この情報にアクセスできます...
Scannotationsの精神でのJavaランタイムメタデータ分析
Reflectionsは、クラスパスをスキャンし、メタデータにインデックスを付け、実行時にクエリを実行できるようにし、プロジェクト内の多くのモジュールの情報を保存および収集できます。Reflectionsを使用すると、次のメタデータを照会できます。
- あるタイプのすべてのサブタイプを取得する
- 何らかの注釈が付けられたすべてのタイプを取得する
- 一致する注釈パラメーターを含む、何らかの注釈が付けられたすべてのタイプを取得します
- いくつかの注釈が付いたすべてのメソッドを取得します
(免責事項:私はそれを使用していませんが、プロジェクトの説明はあなたのニーズにぴったり合っているようです。)
クラス用に生成された Javadoc には、既知のサブクラスのリスト(およびインターフェースの場合は既知の実装クラス)が含まれることを忘れないでください。
数年前にこれをやった。これを行うための最も信頼できる方法(つまり、公式の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に書き込みます。
他の回答に記載されている制限を念頭に置いて、次の方法で openpojoのPojoClassFactory
( Mavenで利用可能 )を使用することもできます。
for(PojoClass pojoClass : PojoClassFactory.enumerateClassesByExtendingType(packageRoot, Superclass.class, null)) {
System.out.println(pojoClass.getClazz());
}
ここで、packageRoot
は検索するパッケージのルート文字列(例:"com.mycompany"
または"com"
)であり、Superclass
はスーパータイプです(これはインターフェースで機能しますまあ)。
私はこのパーティーに数年遅れていることを知っていますが、同じ問題を解決しようとしてこの質問に出会いました。 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();
}
}
私がこれまでに見た最初の問題は、すべてのサブクラスではなく、インターフェイスを直接実装するクラスだけをキャッチしていることです。しかし、少しの再帰は誰も傷つけません。
ClassGraph を試してください。 (免責事項、私は著者です)。 ClassGraphは、実行時またはビルド時のいずれかで、指定されたクラスのサブクラスのスキャンをサポートしますが、さらに多くのスキャンもサポートします。 ClassGraphは、メモリ内、クラスパス上のすべてのクラス、またはホワイトリストに登録されたパッケージ内のクラスについて、クラスグラフ全体(すべてのクラス、注釈、メソッド、メソッドパラメーター、フィールド)の抽象表現を構築できますが、そのクラスグラフをクエリできますあなたが欲しい。 ClassGraphは、他のスキャナーよりも クラスパス指定メカニズムとクラスローダー をサポートしており、新しいJPMSモジュールシステムともシームレスに動作します。そのため、ClassGraphに基づいてコードを作成すると、コードの移植性が最大限になります。 ここのAPIを参照してください
実装とEclipseの違いがわかるのは、毎回スキャンするのに対し、Eclipse(および他のツール)は1回だけスキャンし(ほとんどの場合、プロジェクトのロード中)、インデックスを作成するためです。次回データを要求するとき、再度スキャンすることはありませんが、インデックスを見てください。
同様に、現在のクラスパスに存在するすべてのサブクラスのみが検索されることにも注意してください。おそらくこれはあなたが現在見ているものには問題ありません。おそらくこれを検討した可能性がありますが、ある時点で非final
クラスを(さまざまなレベルの「野生」に対して)リリースした場合他の誰かがあなたが知らない自分のサブクラスを書いていることは完全に実現可能です。
したがって、変更を加えたいためにすべてのサブクラスを表示したい場合、サブクラスの動作にどのように影響するかを確認する場合は、表示できないサブクラスを念頭に置いてください。理想的には、すべての非プライベートメソッド、およびクラス自体を十分に文書化する必要があります。メソッド/非プライベートフィールドのセマンティクスを変更せずに、このドキュメントに従って変更を加えてください。少なくともスーパークラスの定義に従ったサブクラスについては、変更には下位互換性が必要です。
org.reflections.Reflections
を使用して抽象クラスのサブクラスを取得する簡単なデモを作成します。
特定の要件に応じて、Javaの service loader メカニズムが目的を達成する場合があります。
つまり、開発者は、JAR/WARファイルのMETA-INF/services
ディレクトリ内のファイルにリストすることにより、クラスが他のクラスをサブクラス化する(またはインターフェイスを実装する)ことを明示的に宣言できます。その後、 Java.util.ServiceLoader
クラスを使用して検出できます。このクラスは、Class
オブジェクトを指定すると、そのクラスの宣言されたすべてのサブクラスのインスタンスを生成します(または、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.";
}
}
テキストMETA-INF/services/com.example.Example
を含むファイルcom.example.ExampleImpl
を作成することにより、クラスExampleImpl
がExample
の実装であることを宣言します。
その後、次のように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.
私はすべてのサブクラスのクラスパスをスキャンするリフレクションライブラリを使用しています: https://github.com/ronmamo/reflections
これはそれが行われる方法です:
Reflections reflections = new Reflections("my.project");
Set<Class<? extends SomeType>> subTypes = reflections.getSubTypesOf(SomeType.class);
それらを親クラスコンストラクター内の静的マップ(this.getClass()。getName())に追加します(またはデフォルトコンストラクターを作成します)が、これは実行時に更新されます。遅延初期化がオプションの場合、このアプローチを試すことができます。
新しいクラスがコードに追加されたかどうかを確認するために、テストケースとしてこれを行う必要がありました。これは私がやったことです
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());
}
}
}