web-dev-qa-db-ja.com

なぜリフレクションを使用する必要があるのですか?

私はJavaが初めてです。私の研究を通して、リフレクションはクラスとメソッドを呼び出すために使用され、どのメソッドが実装されているかどうかを知るために読んだ。

いつリフレクションを使用する必要がありますか?リフレクションの使用とオブジェクトのインスタンス化と従来の方法でのメソッドの呼び出しの違いは何ですか?

30
Hamzah khammash
  • リフレクションは、プリコンパイルされたアドレスと定数を使用するだけでなく、バ​​イトコードのメタデータを検査する必要があるため、名前でメソッドを呼び出すよりもはるかに遅くなります。

  • リフレクションもより強力です:protectedまたはfinalメンバーの定義を取得し、保護を削除して、変更可能と宣言されているかのように操作できます!これは明らかに、言語がプログラムに対して通常行う多くの保証を覆し、非常に非常に危険な場合があります。

そして、これはそれをいつ使うべきかをかなり説明しています。通常はしないでください。メソッドを呼び出したい場合は、それを呼び出します。メンバーを変更したい場合は、コンパイルの後ろに行くのではなく、変更可能であると宣言するだけです。

リフレクションの実用的な使用法の1つは、ユーザー定義のクラスと相互運用する必要があるフレームワークを作成する場合です。フレームワークの作成者は、メンバー(またはクラス)がどうなるかを知りません。リフレクションを使用すると、事前に知らなくてもクラスを処理できます。たとえば、リフレクションなしに複雑なアスペクト指向のライブラリを作成することは不可能だと思います。

別の例として、JUnitは些細なリフレクションを使用していました。クラスのすべてのメソッドを列挙し、testXXXと呼ばれるメソッドはすべてテストメソッドであると想定し、それらのみを実行します。しかし、これは代わりにアノテーションを使用することでより適切に実行できるようになり、実際、JUnit 4は主にアノテーションに移行しました。

39
Kilian Foth

私はかつてあなたのようでした、私は反射についてあまり知りませんでした-それでもわかりません-しかし、私はそれを一度使用しました。

2つの内部クラスを持つクラスがあり、各クラスには多くのメソッドがありました。

内部クラスのすべてのメソッドを呼び出す必要があり、それらを手動で呼び出すのは大変な作業でした。

リフレクションを使用すると、メソッド自体の数ではなく、これらすべてのメソッドを2〜3行のコードで呼び出すことができます。

17
Mahmoud Hossam

リフレクションの使用を3つのグループにグループ化します。

  1. 任意のクラスをインスタンス化します。たとえば、依存関係注入フレームワークでは、インターフェイスThingDoerがクラスNetworkThingDoerによって実装されることをおそらく宣言します。次に、フレームワークはNetworkThingDoerのコンストラクターを見つけてインスタンス化します。
  2. 他の形式へのマーシャリングおよびアンマーシャリング。たとえば、Beanの規約に従うゲッターと設定を使用してオブジェクトをJSONにマッピングし、再度マッピングします。コードは実際にはフィールドやメソッドの名前を知りませんが、クラスを調べます。
  3. リダイレクトのレイヤーでクラスをラップする(おそらくListは実際にはロードされないが、データベースからそれをフェッチする方法を知っているものへのポインター)またはクラスを完全に偽造する(jMockはインターフェースを実装する合成クラスを作成します)テスト目的で)。
15
Kevin Peterson

リフレクションにより、プログラムは存在しない可能性のあるコードを処理し、信頼できる方法でこれを行うことができます。

「通常のコード」にはURLConnection c = nullのようなスニペットがあり、その存在により、クラスローダーはこのクラスのロードの一部としてURLConnectionクラスをロードし、ClassNotFound例外をスローして終了します。

リフレクションを使用すると、クラスに名前に基づいて文字列形式でクラスをロードし、それらに依存する実際のクラスを起動する前に、さまざまなプロパティ(コントロール外の複数のバージョンに有用)をテストできます。典型的な例は、Javaプログラムを他のプラットフォームには存在しないOS Xでネイティブに見えるようにするために使用されるOS X固有のコードです。

3
user1249

基本的に、リフレクションとはプログラムのコードをデータとして使用することを意味します。

したがって、プログラムのコードが有用なデータソースである場合は、リフレクションを使用することをお勧めします。 (ただし、トレードオフがあるため、常に良いアイデアとは限りません。)

たとえば、単純なクラスを考えます。

public class Foo {
  public int value;
  public string anotherValue;
}

それからXMLを生成したいとします。 XMLを生成するコードを記述できます。

public XmlNode generateXml(Foo foo) {
  XmlElement root = new XmlElement("Foo");
  XmlElement valueElement = new XmlElement("value");
  valueElement.add(new XmlText(Integer.toString(foo.value)));
  root.add(valueElement);
  XmlElement anotherValueElement = new XmlElement("anotherValue");
  anotherValueElement.add(new XmlText(foo.anotherValue));
  root.add(anotherValueElement);
  return root;
}

ただし、これは多くの定型コードであり、クラスを変更するたびにコードを更新する必要があります。本当に、あなたはこのコードが何をするかを説明できます

  • クラスの名前でXML要素を作成する
  • クラスの各プロパティ用
    • プロパティの名前でXML要素を作成する
    • プロパティの値をXML要素に挿入する
    • ルートにXML要素を追加する

これはアルゴリズムであり、アルゴリズムの入力はクラスです。その名前と、そのプロパティの名前、型、および値が必要です。ここでリフレクションが登場します。これにより、この情報にアクセスできます。 Javaを使用すると、Classクラスのメソッドを使用して型を検査できます。

さらにいくつかの使用例:

  • クラスのメソッド名に基づいてWebサーバーにURLを定義し、メソッド引数に基づいてURLパラメーターを定義する
  • クラスの構造をGraphQLタイプ定義に変換する
  • 名前が「test」で始まるクラスのすべてのメソッドを単体テストケースとして呼び出す

ただし、完全なリフレクションとは、既存のコード(それ自体が「イントロスペクション」として知られている)だけでなく、コードの変更または生成も行うことを意味します。 Javaこれには、プロキシとモックの2つの顕著な使用例があります。

あなたがインターフェースを持っているとしましょう:

public interface Froobnicator {
  void froobnicateFruits(List<Fruit> fruits);
  void froobnicateFuel(Fuel fuel);
  // lots of other things to froobnicate
}

そしてあなたは何か面白いことをする実装を持っています:

public class PowerFroobnicator implements Froobnicator {
  // awesome implementations
}

そして実際には、2番目の実装もあります。

public class EnergySaverFroobnicator implements Froobnicator {
  // efficient implementations
}

ここで、ログ出力も必要です。メソッドが呼び出されたときにログメッセージが必要なだけです。すべてのメソッドにログ出力を明示的に追加することもできますが、これは面倒で、2回行う必要があります。実装ごとに1回。 (つまり、実装を追加するとさらに多くなります。)

代わりに、プロキシを作成できます。

public class LoggingFroobnicator implements Froobnicator {
  private Logger logger;
  private Froobnicator inner;

  // constructor that sets those two

  public void froobnicateFruits(List<Fruit> fruits) {
    logger.logDebug("froobnicateFruits called");
    inner.froobnicateFruits(fruits);
  }

  public void froobnicateFuel(Fuel fuel) {
    logger.logDebug("froobnicateFuel( called");
    inner.froobnicateFuel(fuel);
  }
  // lots of other things to froobnicate
}

ただし、繰り返しになりますが、アルゴリズムで記述できる反復的なパターンがあります。

  • ロガープロキシは、インターフェイスを実装するクラスです
  • インターフェースとロガーの別の実装をとるコンストラクタがあります
  • インターフェースのすべてのメソッド
    • 実装はメッセージ「$ methodname called」をログに記録します
    • 次に、内部インターフェイスで同じメソッドを呼び出し、すべての引数を渡します

このアルゴリズムの入力はインターフェース定義です。

リフレクションでは、このアルゴリズムを使用して新しいクラスを定義できます。 Javaでは、Java.lang.reflect.Proxyクラスのメソッドを使用してこれを行うことができます。さらに強力なライブラリがあります。

それでは、反射の欠点は何ですか?

  • コードが理解しにくくなります。あなたは、コードの具体的な効果からさらに取り除かれた1レベルの抽象化です。
  • コードのデバッグが難しくなります。特にコード生成ライブラリの場合、実行されるコードは記述したコードではない可能性がありますが、生成したコードとデバッガーはそのコードを表示できない(またはブレークポイントを配置できる)場合があります。
  • コードが遅くなります。アクセスをハードコーディングする代わりに、型情報を動的に読み取り、ランタイムハンドルでフィールドにアクセスすると、処理が遅くなります。動的コード生成は、デバッグをさらに困難にする代わりに、この影響を緩和できます。
  • コードはより脆弱になる可能性があります。動的リフレクションアクセスはコンパイラーによって型チェックされませんが、実行時にエラーをスローします。
3
Sebastian Redl

Reflectionでは、プログラムの一部を自動的に同期させることができます。以前は、新しいインターフェイスを使用するためにプログラムを手動で更新する必要がありました。

1
DeadMG