グアバのリフレクションユーティリティの手引き

グアバの反射ユーティリティのガイド

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> mapToken
  = new TypeToken>() {};

TypeToken entrySetToken = mapToken
  .resolveType(Map.class.getMethod("entrySet")
  .getGenericReturnType());

assertEquals(
  entrySetToken,
  new TypeToken>>() {});

ここでは、Java標準ライブラリのリフレクションメソッドgetMethod()を使用して、メソッドの戻り値の型をキャプチャします。

4. Invokable

Invokableは、java.lang.reflect.Methodjava.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プロジェクトであるため、そのままインポートして実行するのは簡単です。