Java Language
リフレクションAPI
サーチ…
前書き
リフレクションは、JVMで実行されているアプリケーションの実行時の動作を調べたり変更したりする必要があるプログラムでよく使用されます。 Java Reflection APIは、コンパイル時に名前を知らなくても、実行時にクラス、インタフェース、フィールド、メソッドを検査できるようにする目的で使用されます。また、新しいオブジェクトをインスタンス化し、リフレクションを使用してメソッドを呼び出すこともできます。
備考
パフォーマンス
リフレクションはパフォーマンスを低下させる可能性があります。リフレクションなしでタスクを完了できない場合にのみ使用してください。
JavaチュートリアルReflection APIから :
リフレクションには動的に解決される型が含まれるため、特定のJava仮想マシンの最適化は実行できません。その結果、反射動作は、非反射動作よりも性能が遅くなり、性能重視のアプリケーションで頻繁に呼び出されるコードのセクションでは避けるべきです。
前書き
基本
Reflection APIを使用すると、実行時にコードのクラス構造をチェックし、コードを動的に呼び出すことができます。これは非常に強力ですが、動的呼び出しが有効かどうかをコンパイラが静的に判断できないため、危険です。
簡単な例は、与えられたクラスのpublicコンストラクタとメソッドを取得することです:
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
// This is a object representing the String class (not an instance of String!)
Class<String> clazz = String.class;
Constructor<?>[] constructors = clazz.getConstructors(); // returns all public constructors of String
Method[] methods = clazz.getMethods(); // returns all public methods from String and parents
この情報を使用して、オブジェクトをインスタンス化し、異なるメソッドを動的に呼び出すことができます。
反射と一般的なタイプ
一般的なタイプの情報は、
-
getGenericParameterTypes()
を使用してメソッドのパラメータをgetGenericParameterTypes()
ます。 -
getGenericReturnType()
を使用して、メソッドの戻り値の型を返します。 -
getGenericType
を使用して、 パブリックフィールドをgetGenericType
ます。
次の例は、3つのケースすべてで汎用タイプ情報を抽出する方法を示しています。
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
public class GenericTest {
public static void main(final String[] args) throws Exception {
final Method method = GenericTest.class.getMethod("testMethod", Map.class);
final Field field = GenericTest.class.getField("testField");
System.out.println("Method parameter:");
final Type parameterType = method.getGenericParameterTypes()[0];
displayGenericType(parameterType, "\t");
System.out.println("Method return type:");
final Type returnType = method.getGenericReturnType();
displayGenericType(returnType, "\t");
System.out.println("Field type:");
final Type fieldType = field.getGenericType();
displayGenericType(fieldType, "\t");
}
private static void displayGenericType(final Type type, final String prefix) {
System.out.println(prefix + type.getTypeName());
if (type instanceof ParameterizedType) {
for (final Type subtype : ((ParameterizedType) type).getActualTypeArguments()) {
displayGenericType(subtype, prefix + "\t");
}
}
}
public Map<String, Map<Integer, List<String>>> testField;
public List<Number> testMethod(final Map<String, Double> arg) {
return null;
}
}
これにより、次のような結果が得られます。
Method parameter:
java.util.Map<java.lang.String, java.lang.Double>
java.lang.String
java.lang.Double
Method return type:
java.util.List<java.lang.Number>
java.lang.Number
Field type:
java.util.Map<java.lang.String, java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>>
java.lang.String
java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>
java.lang.Integer
java.util.List<java.lang.String>
java.lang.String
メソッドの呼び出し
リフレクションを使用すると、実行時にオブジェクトのメソッドを呼び出すことができます。
この例は、 String
オブジェクトのメソッドを呼び出す方法を示しています。
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
String s = "Hello World!";
// method without parameters
// invoke s.length()
Method method1 = String.class.getMethod("length");
int length = (int) method1.invoke(s); // variable length contains "12"
// method with parameters
// invoke s.substring(6)
Method method2 = String.class.getMethod("substring", int.class);
String substring = (String) method2.invoke(s, 6); // variable substring contains "World!"
フィールドの取得と設定
Reflection APIを使用すると、実行時にフィールドの値を変更または取得することができます。たとえば、APIのように、OSなどの要因に基づいて異なるフィールドを取得することができます。 final
ような修飾子を削除して、最終的なフィールドを変更できるようにすることもできます。
これを行うには、次のような方法でClass#getField()メソッドを使用する必要があります:
// Get the field in class SomeClass "NAME".
Field nameField = SomeClass.class.getDeclaredField("NAME");
// Get the field in class Field "modifiers". Note that it does not
// need to be static
Field modifiersField = Field.class.getDeclaredField("modifiers");
// Allow access from anyone even if it's declared private
modifiersField.setAccessible(true);
// Get the modifiers on the "NAME" field as an int.
int existingModifiersOnNameField = nameField.getModifiers();
// Bitwise AND NOT Modifier.FINAL (16) on the existing modifiers
// Readup here https://en.wikipedia.org/wiki/Bitwise_operations_in_C
// if you're unsure what bitwise operations are.
int newModifiersOnNameField = existingModifiersOnNameField & ~Modifier.FINAL;
// Set the value of the modifiers field under an object for non-static fields
modifiersField.setInt(nameField, newModifiersOnNameField);
// Set it to be accessible. This overrides normal Java
// private/protected/package/etc access control checks.
nameField.setAccessible(true);
// Set the value of "NAME" here. Note the null argument.
// Pass null when modifying static fields, as there is no instance object
nameField.set(null, "Hacked by reflection...");
// Here I can directly access it. If needed, use reflection to get it. (Below)
System.out.println(SomeClass.NAME);
フィールドの取得ははるかに簡単です。 Field#get()およびその変形を使用して値を取得できます。
// Get the field in class SomeClass "NAME".
Field nameField = SomeClass.class.getDeclaredField("NAME");
// Set accessible for private fields
nameField.setAccessible(true);
// Pass null as there is no instance, remember?
String name = (String) nameField.get(null);
これに注意してください:
Class#getDeclaredFieldを使用する場合は、このクラスを使用してクラス自体のフィールドを取得します。
class HackMe extends Hacked {
public String iAmDeclared;
}
class Hacked {
public String someState;
}
ここで、 HackMe#iAmDeclared
は宣言されたフィールドです。しかし、 HackMe#someState
はスーパークラスHackedから継承されているため、宣言されたフィールドではありません。
コールコンストラクタ
コンストラクタオブジェクトの取得
次のように、 Class
オブジェクトからConstructor
クラスを取得できます。
Class myClass = ... // get a class object
Constructor[] constructors = myClass.getConstructors();
constructors
変数には、クラス内で宣言された各パブリックコンストラクタのConstructor
インスタンスが1つあります。
アクセスしたいコンストラクタの正確なパラメータ型が分かっている場合は、特定のコンストラクタをフィルタすることができます。次の例は、指定されたクラスのパブリックコンストラクタを返します。このコンストラクタは、 Integer
をパラメータとして取ります。
Class myClass = ... // get a class object
Constructor constructor = myClass.getConstructor(new Class[]{Integer.class});
コンストラクタが指定されたコンストラクタ引数に一致しない場合、 NoSuchMethodException
がスローされます。
コンストラクタオブジェクトを使用する新しいインスタンス
Class myClass = MyObj.class // get a class object
Constructor constructor = myClass.getConstructor(Integer.class);
MyObj myObj = (MyObj) constructor.newInstance(Integer.valueOf(123));
列挙定数の取得
この列挙を例として与える:
enum Compass {
NORTH(0),
EAST(90),
SOUTH(180),
WEST(270);
private int degree;
Compass(int deg){
degree = deg;
}
public int getDegree(){
return degree;
}
}
Javaではenumクラスは他のクラスと似ていますが、enum値にはいくつかの定数があります。さらに、すべての値を保持する配列であるフィールドと、名前values()
およびvalueOf(String)
持つ2つの静的メソッドがありvalues()
。
Reflectionを使用してこのクラスのすべてのフィールドを印刷すると、これを見ることができます
for(Field f : Compass.class.getDeclaredFields())
System.out.println(f.getName());
出力は次のようになります。
北
東
南
西
度
ENUM $ VALUES
そこで、他のクラスと同様に、Reflectionでenumクラスを調べることができました。しかし、Reflection APIは3つの列挙型メソッドを提供します。
列挙チェック
Compass.class.isEnum();
列挙型を表すクラスに対してtrueを返します。
値を取得する
Object[] values = Compass.class.getEnumConstants();
Compass.values()のようなすべてのenum値の配列を返しますが、インスタンスは必要ありません。
列挙定数チェック
for(Field f : Compass.class.getDeclaredFields()){
if(f.isEnumConstant())
System.out.println(f.getName());
}
enum値であるすべてのクラスフィールドを一覧表示します。
その(完全修飾された)名前でクラスを取得する
クラスの名前を含むString
が与えられた場合、 Class
オブジェクトはClass.forName
を使ってアクセスできます:
Class clazz = null;
try {
clazz = Class.forName("java.lang.Integer");
} catch (ClassNotFoundException ex) {
throw new IllegalStateException(ex);
}
クラスを初期化する必要がある場合( forName
2番目のパラメータ)、使用するClassLoader
(3番目のパラメータ)を指定することができます。
ClassLoader classLoader = ...
boolean initialize = ...
Class clazz = null;
try {
clazz = Class.forName("java.lang.Integer", initialize, classLoader);
} catch (ClassNotFoundException ex) {
throw new IllegalStateException(ex);
}
リフレクションを使用してオーバーロードされたコンストラクタを呼び出す
例:関連するパラメータを渡して別のコンストラクタを呼び出す
import java.lang.reflect.*;
class NewInstanceWithReflection{
public NewInstanceWithReflection(){
System.out.println("Default constructor");
}
public NewInstanceWithReflection( String a){
System.out.println("Constructor :String => "+a);
}
public static void main(String args[]) throws Exception {
NewInstanceWithReflection object = (NewInstanceWithReflection)Class.forName("NewInstanceWithReflection").newInstance();
Constructor constructor = NewInstanceWithReflection.class.getDeclaredConstructor( new Class[] {String.class});
NewInstanceWithReflection object1 = (NewInstanceWithReflection)constructor.newInstance(new Object[]{"StackOverFlow"});
}
}
出力:
Default constructor
Constructor :String => StackOverFlow
説明:
-
Class.forName
を使用してクラスのインスタンスを作成:デフォルトのコンストラクタを呼び出します。 -
Class array
としてパラメータの型を渡して、クラスのgetDeclaredConstructor
を呼び出しgetDeclaredConstructor
- コンストラクタを取得し
newInstance
、パラメータvalueをObject array
として渡してnewInstance
を作成します。
プライベート変数と最終変数を変更するためのReflection APIの誤用
リフレクションは、適切な目的で適切に使用されている場合に便利です。リフレクションを使用すると、プライベート変数にアクセスして最終変数を再初期化できます。
以下はコードスニペットですが、これはお勧めできません 。
import java.lang.reflect.*;
public class ReflectionDemo{
public static void main(String args[]){
try{
Field[] fields = A.class.getDeclaredFields();
A a = new A();
for ( Field field:fields ) {
if(field.getName().equalsIgnoreCase("name")){
field.setAccessible(true);
field.set(a, "StackOverFlow");
System.out.println("A.name="+field.get(a));
}
if(field.getName().equalsIgnoreCase("age")){
field.set(a, 20);
System.out.println("A.age="+field.get(a));
}
if(field.getName().equalsIgnoreCase("rep")){
field.setAccessible(true);
field.set(a,"New Reputation");
System.out.println("A.rep="+field.get(a));
}
if(field.getName().equalsIgnoreCase("count")){
field.set(a,25);
System.out.println("A.count="+field.get(a));
}
}
}catch(Exception err){
err.printStackTrace();
}
}
}
class A {
private String name;
public int age;
public final String rep;
public static int count=0;
public A(){
name = "Unset";
age = 0;
rep = "Reputation";
count++;
}
}
出力:
A.name=StackOverFlow
A.age=20
A.rep=New Reputation
A.count=25
説明:
通常のシナリオでは、 private
変数は宣言されたクラスの外部(ゲッターメソッドとセッターメソッドなし)にはアクセスできません。 final
変数は初期化後に再割り当てすることはできません。
Reflection
は壊れる上記のように両方の障壁が私的変数と最終変数の両方を変えるために悪用される可能性がある。
field.setAccessible(true)
は、目的の機能を達成するためのキーです。
ネストされたクラスのコンストラクタを呼び出す
内部ネストされたクラスのインスタンスを作成する場合は、 クラス#getDeclaredConstructorを使用して、囲むクラスのクラスオブジェクトを追加のパラメータとして指定する必要があります。
public class Enclosing{
public class Nested{
public Nested(String a){
System.out.println("Constructor :String => "+a);
}
}
public static void main(String args[]) throws Exception {
Class<?> clazzEnclosing = Class.forName("Enclosing");
Class<?> clazzNested = Class.forName("Enclosing$Nested");
Enclosing objEnclosing = (Enclosing)clazzEnclosing.newInstance();
Constructor<?> constructor = clazzNested.getDeclaredConstructor(new Class[]{Enclosing.class, String.class});
Nested objInner = (Nested)constructor.newInstance(new Object[]{objEnclosing, "StackOverFlow"});
}
}
ネストされたクラスが静的である場合、この囲むインスタンスは必要ありません。
動的プロキシ
動的プロキシは実際にはReflectionとはあまり関係がありませんが、APIの一部です。これは、基本的にインターフェイスの動的実装を作成する方法です。これはモックアップサービスを作成するときに役立ちます。
動的プロキシは、すべてのメソッド呼び出しをインターセプトし、呼び出しを手動で処理できる、いわゆる呼び出しハンドラを使用して作成されたインターフェイスのインスタンスです。
public class DynamicProxyTest {
public interface MyInterface1{
public void someMethod1();
public int someMethod2(String s);
}
public interface MyInterface2{
public void anotherMethod();
}
public static void main(String args[]) throws Exception {
// the dynamic proxy class
Class<?> proxyClass = Proxy.getProxyClass(
ClassLoader.getSystemClassLoader(),
new Class[] {MyInterface1.class, MyInterface2.class});
// the dynamic proxy class constructor
Constructor<?> proxyConstructor =
proxyClass.getConstructor(InvocationHandler.class);
// the invocation handler
InvocationHandler handler = new InvocationHandler(){
// this method is invoked for every proxy method call
// method is the invoked method, args holds the method parameters
// it must return the method result
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if(methodName.equals("someMethod1")){
System.out.println("someMethod1 was invoked!");
return null;
}
if(methodName.equals("someMethod2")){
System.out.println("someMethod2 was invoked!");
System.out.println("Parameter: " + args[0]);
return 42;
}
if(methodName.equals("anotherMethod")){
System.out.println("anotherMethod was invoked!");
return null;
}
System.out.println("Unkown method!");
return null;
}
};
// create the dynamic proxy instances
MyInterface1 i1 = (MyInterface1) proxyConstructor.newInstance(handler);
MyInterface2 i2 = (MyInterface2) proxyConstructor.newInstance(handler);
// and invoke some methods
i1.someMethod1();
i1.someMethod2("stackoverflow");
i2.anotherMethod();
}
}
このコードの結果は次のとおりです。
someMethod1 was invoked!
someMethod2 was invoked!
Parameter: stackoverflow
anotherMethod was invoked!
Evil Javaがリフレクションでハックする
Reflection APIは、JDKのデフォルトライブラリでもプライベートフィールドとファイナルフィールドの値を変更するために使用できます。これは、わかるように、よく知られているいくつかのクラスの動作を操作するために使用できます。
不可能なこと
最初の唯一の制限は、Reflectionで変更できない唯一のフィールドで始まります。これがJava SecurityManager
です。 java.lang.Systemで次のように宣言されています。
private static volatile SecurityManager security = null;
しかし、このコードを実行するとSystemクラスにはリストされません
for(Field f : System.class.getDeclaredFields())
System.out.println(f);
理由のザッツfieldFilterMap
でsun.reflect.Reflection
にマップ自体およびセキュリティ分野を保持System.class
とリフレクションを持つ任意のアクセスに対してそれらを保護します。したがって、 SecurityManager
非アクティブ化することはできませんでした。
クレイジーストリングス
各Java Stringは、 String
クラスのインスタンスとしてJVMによって表されます。ただし、状況によっては、JVMは、同じインスタンスを使用してヒープ領域を節約します。これは、文字列リテラルや、 String.intern()
呼び出すことで " String.intern()
"された文字列に対しても発生します。したがって、コード内に"hello"
が複数回ある場合、常に同じオブジェクトインスタンスになります。
文字列は不変であるはずですが、 "邪悪な"反射を使って文字列を変更することは可能です。以下の例は、 value
フィールドを置き換えることによって文字列内の文字を変更する方法を示していvalue
。
public class CrazyStrings {
static {
try {
Field f = String.class.getDeclaredField("value");
f.setAccessible(true);
f.set("hello", "you stink!".toCharArray());
} catch (Exception e) {
}
}
public static void main(String args[]) {
System.out.println("hello");
}
}
だから、このコードは "あなたが悪臭を放つ!"
1 = 42
同じ考え方をIntegerクラスで使うこともできます
public class CrazyMath {
static {
try {
Field value = Integer.class.getDeclaredField("value");
value.setAccessible(true);
value.setInt(Integer.valueOf(1), 42);
} catch (Exception e) {
}
}
public static void main(String args[]) {
System.out.println(Integer.valueOf(1));
}
}
すべてが真実です
そして、 このstackoverflowの投稿によると、本当に悪いことをするためにリフレクションを使うことができます。
public class Evil {
static {
try {
Field field = Boolean.class.getField("FALSE");
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, true);
} catch (Exception e) {
}
}
public static void main(String args[]){
System.out.format("Everything is %s", false);
}
}
私たちがここでやっていることは、JVMを不可解な方法で動作させることになることに注意してください。これは非常に危険です。