グアバの反射ユーティリティのガイド
1. 概要
この記事では、Guavareflection APIについて説明します。これは、標準のJavaリフレクションAPIと比較して間違いなく汎用性があります。
実行時にジェネリック型をキャプチャするためにGuavaを使用し、Invokableもうまく利用します。
2. 実行時にジェネリック型をキャプチャする
In Java, generics are implemented with type erasure.つまり、ジェネリック型の情報はコンパイル時にのみ利用可能であり、実行時には利用できなくなります。
たとえば、List<String>,は、ジェネリック型に関する情報がerased at runtimeを取得します。 そのため、実行時に汎用のClassオブジェクトを渡すことは安全ではありません。
異なるジェネリック型を持つ2つのリストを同じ参照に割り当てることになりますが、これは明らかに良い考えではありません。
List stringList = Lists.newArrayList();
List intList = Lists.newArrayList();
boolean result = stringList.getClass()
.isAssignableFrom(intList.getClass());
assertTrue(result);
型消去のため、メソッドisAssignableFrom()はリストの実際のジェネリック型を知ることができません。 基本的に、実際のタイプに関する情報がない、単なるListである2つのタイプを比較します。
標準のJavaリフレクションAPIを使用することで、メソッドとクラスの一般的なタイプを検出できます。 List<String>を返すメソッドがある場合、リフレクションを使用して、そのメソッドの戻り値の型(List<String>を表すParameterizedType)を取得できます。
TypeTokenクラスはこの回避策を使用して、ジェネリック型の操作を可能にします。 TypeTokenクラスを使用して、ジェネリックリストの実際のタイプをキャプチャし、それらが同じ参照で実際に参照できるかどうかを確認できます。
TypeToken> stringListToken
= new TypeToken>() {};
TypeToken> integerListToken
= new TypeToken>() {};
TypeToken> numberTypeToken
= new TypeToken>() {};
assertFalse(stringListToken.isSubtypeOf(integerListToken));
assertFalse(numberTypeToken.isSubtypeOf(integerListToken));
assertTrue(integerListToken.isSubtypeOf(numberTypeToken));
IntegerクラスはNumberクラス.を拡張するため、integerListTokenのみをタイプnubmerTypeTokenの参照に割り当てることができます。
3. TypeTokenを使用した複雑なタイプのキャプチャ
ジェネリックパラメーター化されたクラスを作成し、実行時にジェネリック型に関する情報を取得したいとします。 その情報を取得するためのフィールドとしてTypeTokenを持つクラスを作成できます。
abstract class ParametrizedClass {
TypeToken type = new TypeToken(getClass()) {};
}
次に、そのクラスのインスタンスを作成すると、実行時にジェネリック型が使用可能になります。
ParametrizedClass parametrizedClass = new ParametrizedClass() {};
assertEquals(parametrizedClass.type, TypeToken.of(String.class));
また、複数のジェネリック型を持つ複合型のTypeTokenを作成し、実行時にそれらの各型に関する情報を取得することもできます。
TypeToken> funToken
= new TypeToken>() {};
TypeToken funResultToken = funToken
.resolveType(Function.class.getTypeParameters()[1]);
assertEquals(funResultToken, TypeToken.of(String.class));
Functionの実際の戻り値の型、つまりString.を取得します。マップ内のエントリの型を取得することもできます。
TypeToken
ここでは、Java標準ライブラリのリフレクションメソッドgetMethod()を使用して、メソッドの戻り値の型をキャプチャします。
4. Invokable
Invokableは、java.lang.reflect.Methodとjava.lang.reflect.Constructorの流暢なラッパーです。 標準のJavareflectionAPIの上にシンプルなAPIを提供します。 2つのパブリックメソッドを持つクラスがあり、そのうちの1つがfinalであるとしましょう。
class CustomClass {
public void somePublicMethod() {}
public final void notOverridablePublicMethod() {}
}
それでは、Guava APIとJava標準のreflection APIを使用してsomePublicMethod()を調べてみましょう。
Method method = CustomClass.class.getMethod("somePublicMethod");
Invokable invokable
= new TypeToken() {}
.method(method);
boolean isPublicStandradJava = Modifier.isPublic(method.getModifiers());
boolean isPublicGuava = invokable.isPublic();
assertTrue(isPublicStandradJava);
assertTrue(isPublicGuava);
これら2つのバリアントの間に大きな違いはありませんが、メソッドがオーバーライド可能であるかどうかを確認することは、Javaでは本当に重要なタスクです。 幸い、InvokableクラスのisOverridable()メソッドを使用すると簡単になります。
Method method = CustomClass.class.getMethod("notOverridablePublicMethod");
Invokable invokable
= new TypeToken() {}.method(method);
boolean isOverridableStandardJava = (!(Modifier.isFinal(method.getModifiers())
|| Modifier.isPrivate(method.getModifiers())
|| Modifier.isStatic(method.getModifiers())
|| Modifier.isFinal(method.getDeclaringClass().getModifiers())));
boolean isOverridableFinalGauava = invokable.isOverridable();
assertFalse(isOverridableStandardJava);
assertFalse(isOverridableFinalGauava);
このような単純な操作でも、標準のreflectionAPIを使用して多くのチェックが必要であることがわかります。 Invokableクラスは、これを使いやすく非常に簡潔なAPIの背後に隠します。
5. 結論
この記事では、GuavaリフレクションAPIを見て、標準のJavaと比較しました。 実行時にジェネリック型をキャプチャする方法と、Invokableクラスがリフレクションを使用するコードにエレガントで使いやすいAPIを提供する方法を見てきました。
これらすべての例とコードスニペットの実装は、GitHub projectにあります。これはMavenプロジェクトであるため、そのままインポートして実行するのは簡単です。