リフレクションとは何ですか? なぜ便利なのですか?
私は特に に興味がJava
ありますが、原則はどの言語でも同じだと思います。
リフレクションという名前は、同じシステム (またはそれ自体) 内の他のコードを検査できるコードを表すために使用されます。
たとえば、Java に不明なタイプのオブジェクトがあり、存在する場合は「doSomething」メソッドを呼び出したいとします。Java の静的型付けシステムは、オブジェクトが既知のインターフェイスに準拠していない限り、実際にはこれをサポートするように設計されていませんが、リフレクションを使用すると、コードでオブジェクトを調べて、「doSomething」というメソッドがあるかどうかを確認し、必要な場合はそれを呼び出すことができます。をしたい。
したがって、Java でのコード例を示すと (問題のオブジェクトが foo であると想像してください):
Method method = foo.getClass().getMethod("doSomething", null);
method.invoke(foo, null);
Java での非常に一般的な使用例の 1 つは、注釈の使用です。たとえば、JUnit 4 はリフレクションを使用してクラスを調べ、@Test アノテーションでタグ付けされたメソッドを探し、単体テストの実行時にそれらを呼び出します。
http://docs.oracle.com/javase/tutorial/reflect/index.htmlには、開始するための優れたリフレクションの例がいくつかあります。
そして最後に、はい、概念は、リフレクションをサポートする他の静的型付け言語 (C# など) とほとんど同じです。動的に型付けされた言語では、上記の使用例はそれほど必要ではありません (コンパイラーは任意のオブジェクトに対して任意のメソッドを呼び出すことを許可し、存在しない場合は実行時に失敗するため)、マークされているメソッドまたは特定の方法で作業することはまだ一般的です。
コメントからの更新:
システム内のコードを検査してオブジェクト タイプを確認する機能は、リフレクションではなく、タイプ イントロスペクションです。リフレクションは、イントロスペクションを利用して実行時に変更を加える機能です。一部の言語はイントロスペクションをサポートしていますが、リフレクションはサポートしていないため、ここでは区別が必要です。そのような例の 1 つが C++ です。
リフレクションは、実行時にクラス、メソッド、属性などを調べて動的に呼び出す言語の機能です。
たとえば、Java のすべてのオブジェクトには methodgetClass()
があり、これにより、コンパイル時にオブジェクトのクラスがわからない場合でも (たとえば、 として宣言した場合Object
)、オブジェクトのクラスを決定できます。これは些細なことに思えるかもしれませんが、そのような反映は不可能です。などのあまり動的でない言語でC++
。より高度な用途では、メソッド、コンストラクターなどをリストして呼び出すことができます。
リフレクションは、コンパイル時にすべてを「知る」必要のないプログラムを作成できるため、実行時に結合できるため、プログラムをより動的にすることができるため、重要です。コードは既知のインターフェイスに対して記述できますが、使用する実際のクラスは、構成ファイルからのリフレクションを使用してインスタンス化できます。
最新のフレームワークの多くは、まさにこの理由でリフレクションを広範囲に使用しています。他のほとんどの現代言語も同様にリフレクションを使用しており、スクリプト言語 (Python など) では、これらの言語の一般的なプログラミング モデル内でより自然に感じられるため、より緊密に統合されています。
リフレクションの私のお気に入りの使用法の 1 つは、以下の Java ダンプ メソッドです。任意のオブジェクトをパラメーターとして取り、Java リフレクション API を使用してすべてのフィールド名と値を出力します。
import java.lang.reflect.Array;
import java.lang.reflect.Field;
public static String dump(Object o, int callCount) {
callCount++;
StringBuffer tabs = new StringBuffer();
for (int k = 0; k < callCount; k++) {
tabs.append("\t");
}
StringBuffer buffer = new StringBuffer();
Class oClass = o.getClass();
if (oClass.isArray()) {
buffer.append("\n");
buffer.append(tabs.toString());
buffer.append("[");
for (int i = 0; i < Array.getLength(o); i++) {
if (i < 0)
buffer.append(",");
Object value = Array.get(o, i);
if (value.getClass().isPrimitive() ||
value.getClass() == java.lang.Long.class ||
value.getClass() == java.lang.String.class ||
value.getClass() == java.lang.Integer.class ||
value.getClass() == java.lang.Boolean.class
) {
buffer.append(value);
} else {
buffer.append(dump(value, callCount));
}
}
buffer.append(tabs.toString());
buffer.append("]\n");
} else {
buffer.append("\n");
buffer.append(tabs.toString());
buffer.append("{\n");
while (oClass != null) {
Field[] fields = oClass.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
buffer.append(tabs.toString());
fields[i].setAccessible(true);
buffer.append(fields[i].getName());
buffer.append("=");
try {
Object value = fields[i].get(o);
if (value != null) {
if (value.getClass().isPrimitive() ||
value.getClass() == java.lang.Long.class ||
value.getClass() == java.lang.String.class ||
value.getClass() == java.lang.Integer.class ||
value.getClass() == java.lang.Boolean.class
) {
buffer.append(value);
} else {
buffer.append(dump(value, callCount));
}
}
} catch (IllegalAccessException e) {
buffer.append(e.getMessage());
}
buffer.append("\n");
}
oClass = oClass.getSuperclass();
}
buffer.append(tabs.toString());
buffer.append("}\n");
}
return buffer.toString();
}
リフレクションの使用
リフレクションは、Java 仮想マシンで実行されているアプリケーションの実行時の動作を調べたり変更したりする機能を必要とするプログラムでよく使用されます。これは比較的高度な機能であり、言語の基礎をしっかりと理解している開発者のみが使用する必要があります。その注意点を念頭に置いて、リフレクションは強力な手法であり、アプリケーションが他の方法では不可能な操作を実行できるようにすることができます。
拡張機能
アプリケーションは、完全修飾名を使用して拡張オブジェクトのインスタンスを作成することにより、外部のユーザー定義クラスを利用できます。クラス ブラウザとビジュアル開発環境 クラス ブラウザは、クラスのメンバーを列挙できる必要があります。ビジュアル開発環境では、リフレクションで利用可能な型情報を利用して、開発者が正しいコードを記述できるようにすることでメリットが得られます。デバッガーとテスト ツール デバッガーは、クラス内のプライベート メンバーを検査できる必要があります。テスト ハーネスは、リフレクションを利用して、クラスで定義された検出可能なセット API を体系的に呼び出し、テスト スイートで高レベルのコード カバレッジを確保できます。
リフレクションの欠点
リフレクションは強力ですが、むやみに使うべきではありません。リフレクションを使用せずに操作を実行できる場合は、リフレクションを使用しないことをお勧めします。リフレクション経由でコードにアクセスする場合は、次の点に注意してください。
リフレクションには動的に解決される型が含まれるため、特定の Java 仮想マシンの最適化は実行できません。したがって、リフレクト操作は、リフレクション操作を行わない操作よりもパフォーマンスが低下するため、パフォーマンスが重視されるアプリケーションで頻繁に呼び出されるコードのセクションでは避ける必要があります。
リフレクションには、セキュリティ マネージャーの下で実行している場合に存在しない可能性があるランタイム アクセス許可が必要です。これは、アプレットなどの制限されたセキュリティ コンテキストで実行する必要があるコードの重要な考慮事項です。
リフレクションを使用すると、プライベート フィールドやメソッドへのアクセスなど、非リフレクション コードでは不正な操作をコードで実行できるため、リフレクションを使用すると予期しない副作用が発生し、コードが機能しなくなり、移植性が損なわれる可能性があります。リフレクティブ コードは抽象化を破るため、プラットフォームのアップグレードで動作が変わる可能性があります。
ソース:リフレクション API
リフレクションは、アプリケーションまたはフレームワークが、まだ作成されていない可能性のあるコードを操作できるようにするための重要なメカニズムです。
たとえば、典型的な web.xml ファイルを考えてみましょう。これには、ネストされた servlet-class 要素を含むサーブレット要素のリストが含まれます。サーブレット コンテナは web.xml ファイルを処理し、リフレクションを通じて各サーブレット クラスの新しいインスタンスを作成します。
もう 1 つの例は、Java API for XML Parsing (JAXP)です。XML パーサー プロバイダーは、よく知られているシステム プロパティを介して「プラグイン」されており、リフレクションを通じて新しいインスタンスを構築するために使用されます。
そして最後に、最も包括的な例は、リフレクションを使用して Bean を作成し、プロキシを多用するSpringです。
すべての言語がリフレクションをサポートしているわけではありませんが、原則は通常、リフレクションをサポートしている言語で同じです。
リフレクションとは、プログラムの構造を「反映」する機能です。またはより具体的に。オブジェクトとクラスを調べて、それらが実装するメソッド、フィールド、およびインターフェイスに関する情報をプログラムで取得する。注釈なども見ることができます。
多くの状況で役立ちます。クラスをコードに動的にプラグインできるようにしたい場所ならどこでも。多くのオブジェクト リレーショナル マッパーは、リフレクションを使用して、使用するオブジェクトを事前に知らなくてもデータベースからオブジェクトをインスタンス化できるようにします。プラグイン アーキテクチャは、リフレクションが役立つもう 1 つの場所です。このような状況では、コードを動的にロードして、プラグインとして使用する適切なインターフェースを実装する型があるかどうかを判断できることが重要です。
Java Reflection は非常に強力で、非常に便利です。Java Reflection を使用すると、コンパイル時にクラス、メソッドなどの名前を知らなくても、実行時にクラス、インターフェース、フィールド、およびメソッドを検査できます。リフレクションを使用して、新しいオブジェクトをインスタンス化し、メソッドを呼び出し、フィールド値を取得/設定することもできます。
リフレクションの使用方法を示す簡単な Java リフレクションの例:
Method[] methods = MyObject.class.getMethods();
for(Method method : methods){
System.out.println("method = " + method.getName());
}
この例では、MyObject というクラスから Class オブジェクトを取得します。この例では、クラス オブジェクトを使用して、そのクラスのメソッドのリストを取得し、メソッドを反復して、それらの名前を出力します。
編集:ほぼ1年後、リフレクションについて読んでいるときにリフレクションの使用がほとんどなくなったため、この回答を編集しています。
<bean id="someID" class="com.example.Foo">
<property name="someField" value="someValue" />
</bean>
Spring コンテキストがこの < bean > 要素を処理するとき、Class.forName(String) を引数「com.example.Foo」とともに使用して、その Class をインスタンス化します。
その後、再びリフレクションを使用して < property > 要素の適切なセッターを取得し、その値を指定された値に設定します。
プライベート メソッドの場合、
Method method = targetClass.getDeclaredMethod(methodName, argClasses);
method.setAccessible(true);
return method.invoke(targetObject, argObjects);
プライベートフィールドについては、
Field field = targetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
例:
API メソッドを使用して取得したオブジェクトをアプリケーションに提供するリモート アプリケーションを例にとります。オブジェクトに基づいて、ある種の計算を実行する必要がある場合があります。
プロバイダーは、オブジェクトが 3 つのタイプであり、オブジェクトのタイプに基づいて計算を実行する必要があることを保証します。
そのため、それぞれが異なるロジックを含む 3 つのクラスで実装する場合があります。明らかに、オブジェクト情報は実行時に利用できるため、計算を実行するために静的にコーディングすることはできません。プロバイダから受け取ったオブジェクト。
リフレクションの簡単な例。チェス ゲームでは、実行時にユーザーが何を動かすかわかりません。リフレクションを使用して、実行時に既に実装されているメソッドを呼び出すことができます。
public class Test {
public void firstMoveChoice(){
System.out.println("First Move");
}
public void secondMOveChoice(){
System.out.println("Second Move");
}
public void thirdMoveChoice(){
System.out.println("Third Move");
}
public static void main(String[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Test test = new Test();
Method[] method = test.getClass().getMethods();
//firstMoveChoice
method[0].invoke(test, null);
//secondMoveChoice
method[1].invoke(test, null);
//thirdMoveChoice
method[2].invoke(test, null);
}
}
私の理解によると:
リフレクションにより、プログラマはプログラム内のエンティティに動的にアクセスできます。つまり、プログラマーがクラスまたはそのメソッドを認識していない場合、アプリケーションのコーディング中に、リフレクションを使用してそのようなクラスを (実行時に) 動的に利用できます。
クラス名が頻繁に変更されるシナリオでよく使用されます。このような状況が発生した場合、プログラマーがアプリケーションを書き直し、クラスの名前を何度も変更することは複雑です。
代わりに、リフレクションを使用することで、クラス名が変更される可能性について心配する必要があります。
リフレクションは、プログラムの実行時情報にアクセスし、その動作を変更できる関数のセットです (いくつかの制限があります)。
プログラムのメタ情報に応じて実行時の動作を変更できるので便利です。つまり、関数の戻り値の型を確認したり、状況の処理方法を変更したりできます。
たとえば C# では、実行時にアセンブリ (.dll) を読み込んで調べ、クラスをナビゲートし、見つけたものに従ってアクションを実行できます。また、実行時にクラスのインスタンスを作成したり、そのメソッドを呼び出したりすることもできます。
どこで役に立ちますか?毎回役立つわけではありませんが、具体的な状況で役立ちます。たとえば、ロギング目的でクラスの名前を取得したり、構成ファイルで指定された内容に従ってイベントのハンドラーを動的に作成したりするために使用できます...
Javaのドキュメントページから
java.lang.reflect
パッケージは、クラスとオブジェクトに関するリフレクション情報を取得するためのクラスとインターフェースを提供します。リフレクションを使用すると、ロードされたクラスのフィールド、メソッド、およびコンストラクターに関する情報にプログラムからアクセスできます。また、リフレクションされたフィールド、メソッド、およびコンストラクターを使用して、セキュリティ制限内で、基になる対応するクラスを操作できます。
AccessibleObject
必要なものが利用可能な場合、アクセス チェックの抑制を許可しReflectPermission
ます。
このパッケージのクラスは、デバッガー、インタープリター、オブジェクト インスペクター、クラス ブラウザー、および などのサービスなどのアプリケーションに対応java.lang.Class
し、ターゲット オブジェクトのパブリック メンバー (そのランタイム クラスに基づく) または によって宣言されたメンバーのいずれかにアクセスする必要があります。与えられたクラスObject Serialization
JavaBeans
以下の機能が含まれます。
クラスによって公開されるメソッドについては、このドキュメントリンクを参照してください。Class
この記事(Sosnoski Software Solutions, Inc. のプレジデントである Dennis Sosnoski による記事) とこの記事(security-explorations pdf) から:
リフレクションを使用するよりもかなりの欠点が見られます
リフレクションのユーザー:
リフレクションの欠点:
一般的な虐待:
リフレクション機能の悪用に関するこの SE の質問をご覧ください。
Javaでプライベートフィールドを読み取るにはどうすればよいですか?
概要:
システムコード内から実行されるその機能の安全でない使用も、Java セキュリティモードの侵害に容易につながる可能性があります。したがって、この機能は控えめに使用してください
リフレクションを使用すると、より汎用的なコードを記述できます。実行時にオブジェクトを作成し、実行時にそのメソッドを呼び出すことができます。したがって、プログラムを高度にパラメータ化することができます。また、オブジェクトとクラスをイントロスペクトして、外部世界に公開されている変数とメソッドを検出することもできます。
Reflection
多くの用途があります。私がよく知っているのは、その場でコードを作成できることです。
IE: 動的クラス、関数、コンストラクター - 任意のデータに基づく (xml/array/sql results/hardcoded/etc..)
重要
Java 9 以降、package-info.java がリフレクション アクセスに対してモジュールを開かない限り、リフレクションを使用できなくなりました。
デフォルトでは、「リフレクション」アクセスはモジュール内のすべてのパッケージに対して拒否されます。
Java 9 モジュールについてを参照してください。