リフレクション API

Java SE API におけるリフレクション

Java SE にはリフレクション機能によりクラスのコンストラクタ、メソッド、フィールド、その他クラスを構成する要素に対して関節参照を行うことができます。Java SE 5.0 でアノテーションが登場し、ランタイムでアノテーションの情報を取得する方法としてリフレクションが拡張されました。また、昨今の Ease of Development(かんたん開発)の実現手段として、リフレクションは欠かせないものとなっています。

一方で、リフレクションは例外処理の煩雑さでも知られます。リフレクション処理においては、様々な種類の例外がスローされる可能性があります。これはリフレクションが必ずしも成功するとは限らないという前提で API が設計されているためです。具体的には、以下の例外がスローされます。

  • InstantiationException - コンストラクタのリフレクションでスローされる場合があります。Class#newInstance() または Constructor#newInstance() でインスタンス化を試みたが、抽象クラス、インタフェース、配列、プリミティブ型であるためインスタンスを生成できなかった場合、または Class#newInstance() でインスタンスを試みたが引数のないコンストラクタが存在しなかった場合にスローされます。リフレクションの対象が具象クラスであり、既知の public コンストラクタ(例えばデフォルト・コンストラクタ)を呼び出す場合にはこの例外はスローされることはありません。
  • SecurityException - フィールドのリフレクションでスローされる場合があります。フィールドへのアクセスがセキュリティ・マネージャーによって制限されていることを表します。Java SE のデフォルトではセキュリティ・マネージャーは無効になっているので、実際にはセキュリティ・マネージャーを設定しない限りこの例外はスローされません。
  • IllegalAccessException - コンストラクタ、メソッド、フィールドのリフレクションでスローされる場合があります。この例外は対象となるコンストラクタ、メソッド、フィールドが private スコープのため呼び出すことができないことを示します。public スコープのコンストラクタ、メソッド、フィールドへのアクセスにおいてこの例外がスローされることはありません。また、スコープによるアクセス制限は一時的に解除することができるので、その場合にもこの例外はスローされません。
  • IllegalArgumentException - コンストラクタの newInstance()、メソッドの invoke()、フィールド のgetField() および setField() 呼び出しの際にスローされる場合があります。Java SE のリフレクション API では仮引数に Object またはその配列を持つため、実際に呼び出されるコンストラクタ、メソッドのシグネチャと一致しない引数や、フィールドと代入互換性のない値取得・設定が行われる場合があります。そのような場合にこの例外がスローされます。既知のコンストラクタ、メソッド、フィールドへのアクセスによってこの例外がスローされる場合はプログラムのバグが考えられます。
  • InvocationTargetException - コンストラクタまたはメソッドの呼び出しでスローされる例外です。この例外は呼び出したコンストラクタまたはメソッドが何らかの例外(またはエラー)をスローした場合に、原因となった例外をラップして再スローされる例外です。実際の例外処理では、例外オブジェクトのgetCause() メソッドで原因となる例外(またはエラー)を取得して、それに基づいて例外処理を行う必要があります。

上記に示す通り、Java SE のリフレクション API は、リフレクション処理に失敗した原因を詳細にトレースできるように例外機構を設計しています。しかし一方では、構成管理上リフレクションが成功することが確実な状況下においても、これら多くの例外を処理しなければならず、このことはプログラマーにとって苦痛となります。このような場合の例外処理はあまり意味をなさず、いたずらにソースコードを煩雑にするだけのものとなります。

voyager-api でのリフレクション

voyager-api では、リフレクションを Reflector クラスを中心として整理しています。Reflector クラスは、特定のクラスのコンストラクタ、メソッド、フィールドへのアクセスを「委譲」されるもので、そのクラスで宣言されている public コンストラクタと、すべてのメソッド・フィールドに対する統一的なアクセスを提供します。

Reflector クラスは初期化時に委譲元のクラスが宣言するすべてのコンストラクタ・メソッド・フィールドのオブジェクトを読み込みます。これらのオブジェクトはクラスロード時に確定しているため、Reflector クラスの初期化は各クラスについて1回で済みます。Reflector クラスのコンストラクタはパッケージ・プライベートとして宣言されているため、直接 Reflector クラスをインスタンス化することはできず、getReflector ファクトリーメソッドから必要なインスタンスを取得します。

Reflector オブジェクトは、任意のタイミングで委譲元のメソッド・フィールドへのアクセスを提供します。コンストラクタ呼び出し、メソッドまたはフィールドへのアクセス中に何らかの例外が発生した場合には、原因例外をラップした ReflectorException、実行時例外、エラーのいずれかをスローします。これらは非キャッチ例外のため、必要がなければプログラム中で例外処理を行う必要はありません。