Javaリフレクションガイド
1概要
この記事では、クラス、インターフェース、フィールド、およびメソッドの実行時属性を検査または変更することを可能にするJavaリフレクションを検討します。これは、コンパイル時に名前がわからないときに特に便利です。
さらに、リフレクションを使用して、新しいオブジェクトをインスタンス化し、メソッドを呼び出し、フィールド値を取得または設定できます。
2プロジェクト設定
-
Javaリフレクションを使用するために、特別なjar、特別な設定、Mavenの依存関係を含める必要はありません。 JDKにはhttps://docs.oracle.com/javase/8/docs/api/java/lang/reflect/package-summary.html[
java.lang.reflect
]パッケージにバンドルされたクラスのグループが付属しています特にこの目的のために。
だから私たちがする必要があるのは私たちのコードに次のインポートをすることです。
import java.lang.reflect.** ;
そして私達は行ってもいいです。
インスタンスのクラス、メソッド、およびフィールド情報にアクセスするには、オブジェクトの実行時クラス表現を返す
getClass
メソッドを呼び出します。返された
class
オブジェクトは、クラスに関する情報にアクセスするためのメソッドを提供します。
3簡単な例
足を濡らすために、実行時に単純なJavaオブジェクトのフィールドを検査する非常に基本的な例を見ていきます。
name
フィールドと
age
フィールドのみを持ち、メソッドをまったく持たない単純な
Person
クラスを作成しましょう。これがPersonクラスです。
public class Person {
private String name;
private int age;
}
このクラスのすべてのフィールドの名前を見つけるために、今度はJavaリフレクションを使用します。リフレクションの力を理解するために、
Person
オブジェクトを作成し、Objectを参照型として使用します。
@Test
public void givenObject__whenGetsFieldNamesAtRuntime__thenCorrect() {
Object person = new Person();
Field[]fields = person.getClass().getDeclaredFields();
List<String> actualFieldNames = getFieldNames(fields);
assertTrue(Arrays.asList("name", "age")
.containsAll(actualFieldNames));
}
このテストは、オブジェクトへの参照がそのオブジェクトの親型であっても、
person
オブジェクトから
Field
オブジェクトの配列を取得できることを示しています。
上記の例では、私たちはそれらのフィールドの名前だけに興味を持っていました、しかしすることができるはるかに多くがあります、そして、私たちは後続のセクションでこれのさらなる例を見るでしょう。
実際のフィールド名を抽出するためにどのようにヘルパーメソッドを使用するかに注目してください。これは非常に基本的なコードです。
private static List<String> getFieldNames(Field[]fields) {
List<String> fieldNames = new ArrayList<>();
for (Field field : fields)
fieldNames.add(field.getName());
return fieldNames;
}
4 Javaリフレクションのユースケース
Javaリフレクションのさまざまな機能に進む前に、それを見つけるための一般的な用途について説明します。 Javaリフレクションは非常に強力で、さまざまな点で非常に便利です。
たとえば、多くの場合、データベーステーブルには命名規則があります。生徒データを含むテーブルが
tbl
student
data
となるように、テーブル名を
tbl
__で事前に固定することで一貫性を追加することを選択できます。
そのような場合は、生徒データを保持するJavaオブジェクトに
Student
または
StudentData.という名前を付けます。次に、CRUDパラダイムを使用して、
Create
操作が
Object__パラメーターのみを受け取るように、各操作ごとに1つのエントリポイントを持ちます。
その後、リフレクションを使用してオブジェクト名とフィールド名を取得します。この時点で、このデータをDBテーブルにマップし、オブジェクトフィールドの値を適切なDBフィールド名に割り当てることができます。
5 Javaクラスの検査
このセクションでは、JavaリフレクションAPIの最も基本的なコンポーネントを探ります。前述したように、Javaクラスオブジェクトは、任意のオブジェクトの内部詳細へのアクセスを可能にします。
オブジェクトのクラス名、それらの修飾子、フィールド、メソッド、実装されたインターフェースなどの内部の詳細を調べます。
5.1. 準備をする
リフレクションAPIをしっかりと把握するために、Javaクラスに適用され、さまざまな例があるように、
Eating
インターフェースを実装する抽象
Animal
クラスを作成します。このインタフェースは、作成した具体的な
Animal
オブジェクトの摂食動作を定義します。
だから最初に、これは
Eating
インターフェースです:
public interface Eating {
String eats();
}
それから
Eating
インターフェースの具体的な
Animal
実装:
public abstract class Animal implements Eating {
public static String CATEGORY = "domestic";
private String name;
protected abstract String getSound();
//constructor, standard getters and setters omitted
}
動物の動きを記述する
Locomotion
という別のインターフェイスも作成しましょう。
public interface Locomotion {
String getLocomotion();
}
Animal
を拡張して
Locomotion
を実装する
Goat
という具象クラスを作成します。スーパークラスは
Eating
を実装しているので、
Goat
はそのインターフェースのメソッドも実装する必要があります。
public class Goat extends Animal implements Locomotion {
@Override
protected String getSound() {
return "bleat";
}
@Override
public String getLocomotion() {
return "walks";
}
@Override
public String eats() {
return "grass";
}
//constructor omitted
}
これ以降は、上記のクラスとインタフェースに現れるJavaオブジェクトの側面を調べるために、Javaリフレクションを使用します。
5.2. クラス名
Class
からオブジェクトの名前を取得することから始めましょう。
@Test
public void givenObject__whenGetsClassName__thenCorrect() {
Object goat = new Goat("goat");
Class<?> clazz = goat.getClass();
assertEquals("Goat", clazz.getSimpleName());
assertEquals("com.baeldung.reflection.Goat", clazz.getName());
assertEquals("com.baeldung.reflection.Goat", clazz.getCanonicalName());
}
Class
の
getSimpleName
メソッドは、宣言に表示されるとおりにオブジェクトの基本名を返します。その後、他の2つのメソッドは、パッケージ宣言を含む完全修飾クラス名を返します。
完全修飾クラス名しかわからない場合は、
Goat
クラスのオブジェクトを作成する方法も見てみましょう。
@Test
public void givenClassName__whenCreatesObject__thenCorrect(){
Class<?> clazz = Class.forName("com.baeldung.reflection.Goat");
assertEquals("Goat", clazz.getSimpleName());
assertEquals("com.baeldung.reflection.Goat", clazz.getName());
assertEquals("com.baeldung.reflection.Goat", clazz.getCanonicalName());
}
静的な
forName
メソッドに渡す名前にはパッケージ情報を含める必要があります。それ以外の場合は
ClassNotFoundException
が返されます。
5.3. クラス修飾子
Integerを返す
getModifiers__メソッドを呼び出すことで、クラスで使用されている修飾子を決定できます。各修飾子は、設定またはクリアされるフラグビットです。
java.lang.reflect.Modifier
クラスは、返された
Integer
のプレゼンスを分析する静的メソッドを提供します。特定の修飾子がない。
上記で定義したクラスのいくつかの修飾子を確認しましょう。
@Test
public void givenClass__whenRecognisesModifiers__thenCorrect() {
Class<?> goatClass = Class.forName("com.baeldung.reflection.Goat");
Class<?> animalClass = Class.forName("com.baeldung.reflection.Animal");
int goatMods = goatClass.getModifiers();
int animalMods = animalClass.getModifiers();
assertTrue(Modifier.isPublic(goatMods));
assertTrue(Modifier.isAbstract(animalMods));
assertTrue(Modifier.isPublic(animalMods));
}
プロジェクトにインポートしているライブラリjar内にあるクラスの修飾子を調べることができます。
ほとんどの場合、本格的なインスタンス化ではなく
forName
アプローチを使用する必要があるかもしれません。それは、メモリが重いクラスの場合には高価なプロセスになるからです。
5.4. パッケージ情報
Javaリフレクションを使用することで、クラスやオブジェクトのパッケージに関する情報を取得することもできます。このデータは、クラスオブジェクトの
getPackage
メソッドの呼び出しによって返されるhttps://docs.oracle.com/javase/8/docs/api/java/lang/Package.html[
Package
]クラス内にバンドルされています。
テストを実行してパッケージ名を取得しましょう。
@Test
public void givenClass__whenGetsPackageInfo__thenCorrect() {
Goat goat = new Goat("goat");
Class<?> goatClass = goat.getClass();
Package pkg = goatClass.getPackage();
assertEquals("com.baeldung.reflection", pkg.getName());
}
5.5. スーパークラス
Javaリフレクションを使用して、任意のJavaクラスのスーパークラスを取得することもできます。
多くの場合、特にライブラリクラスやjavaの組み込みクラスを使用している間は、使用しているオブジェクトのスーパークラスが事前にわからないことがあります。このサブセクションでは、この情報を取得する方法を説明します。
それでは、先に進み、
Goatのスーパークラスを決定します。さらに、
java.lang.String
classが
java.lang.Object__ classのサブクラスであることも示します。
@Test
public void givenClass__whenGetsSuperClass__thenCorrect() {
Goat goat = new Goat("goat");
String str = "any string";
Class<?> goatClass = goat.getClass();
Class<?> goatSuperClass = goatClass.getSuperclass();
assertEquals("Animal", goatSuperClass.getSimpleName());
assertEquals("Object", str.getClass().getSuperclass().getSimpleName());
}
5.6. 実装されているインタフェース
Javaリフレクションを使用して、特定のクラスによって実装されたインタフェースのリストを取得することもできます。
Goat
クラスと
Animal
抽象クラスによって実装されたインタフェースのクラス型を取得しましょう。
@Test
public void givenClass__whenGetsImplementedInterfaces__thenCorrect(){
Class<?> goatClass = Class.forName("com.baeldung.reflection.Goat");
Class<?> animalClass = Class.forName("com.baeldung.reflection.Animal");
Class<?>[]goatInterfaces = goatClass.getInterfaces();
Class<?>[]animalInterfaces = animalClass.getInterfaces();
assertEquals(1, goatInterfaces.length);
assertEquals(1, animalInterfaces.length);
assertEquals("Locomotion", goatInterfaces[0].getSimpleName());
assertEquals("Eating", animalInterfaces[0].getSimpleName());
}
各クラスは単一のインタフェースのみを実装していることがアサーションからわかります。これらのインタフェースの名前を調べると、
Goat
は
Locomotion
を実装し、
Animal
は
Eating
を実装しています。
Goat
は抽象クラス
Animal
のサブクラスであり、インタフェースメソッド
eats()
を実装していることに気付いたかもしれません。そして、
Goat
も
Eating
インタフェースを実装しています。
したがって、
implements
キーワードを使用して実装されているとクラスが明示的に宣言しているインタフェースだけが、返される配列に表示されることに注意する価値があります。
つまり、スーパークラスがそのインターフェイスを実装するためにクラスがインターフェイスメソッドを実装していても、サブクラスがそのインターフェイスを
implements
キーワードで直接宣言しない場合、そのインターフェイスはインターフェイスの配列に表示されません。
5.7. コンストラクタ、メソッド、およびフィールド
Javaリフレクションを使用すると、オブジェクトのクラスやメソッドやフィールドのコンストラクタを調べることができます。
後でクラスのこれらの各コンポーネントの詳細な検査を見ることができるようになるでしょうが、現時点では、単にそれらの名前を取得し、それらを期待するものと比較するだけで十分です。
Goat
クラスのコンストラクタを取得する方法を見てみましょう。
@Test
public void givenClass__whenGetsConstructor__thenCorrect(){
Class<?> goatClass = Class.forName("com.baeldung.reflection.Goat");
Constructor<?>[]constructors = goatClass.getConstructors();
assertEquals(1, constructors.length);
assertEquals("com.baeldung.reflection.Goat", constructors[0].getName());
}
以下のように
Animal
クラスのフィールドを調べることもできます。
@Test
public void givenClass__whenGetsFields__thenCorrect(){
Class<?> animalClass = Class.forName("com.baeldung.java.reflection.Animal");
Field[]fields = animalClass.getDeclaredFields();
List<String> actualFields = getFieldNames(fields);
assertEquals(2, actualFields.size());
assertTrue(actualFields.containsAll(Arrays.asList("name", "CATEGORY")));
}
Animal
クラスのメソッドを調べることができるのと同じように、
@Test
public void givenClass__whenGetsMethods__thenCorrect(){
Class<?> animalClass = Class.forName("com.baeldung.java.reflection.Animal");
Method[]methods = animalClass.getDeclaredMethods();
List<String> actualMethods = getMethodNames(methods);
assertEquals(4, actualMethods.size());
assertTrue(actualMethods.containsAll(Arrays.asList("getName",
"setName", "getSound")));
}
getFieldNames
と同様に、
Method
オブジェクトの配列からメソッド名を取得するためのヘルパーメソッドを追加しました。
private static List<String> getMethodNames(Method[]methods) {
List<String> methodNames = new ArrayList<>();
for (Method method : methods)
methodNames.add(method.getName());
return methodNames;
}
6. コンストラクタの検査
Javaリフレクションを使用すると、任意のクラスのコンストラクタを検査し、さらに実行時にクラスオブジェクトを作成することもできます。これはhttps://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Constructor.html[
java.lang.reflect.Constructor
]クラスによって可能になります。
先ほど、
__constructor
__オブジェクトの配列を取得する方法のみを調べました。そこからコンストラクタの名前を取得できました。
このセクションでは、特定のコンストラクタを取得する方法に焦点を当てます。
Javaでは、私たちが知っているように、クラスの2つのコンストラクタがまったく同じメソッドシグネチャを共有することはありません。そのため、この一意性を利用して多数のコンストラクタから1つのコンストラクタを取得します。
このクラスの機能を理解するために、3つのコンストラクタを持つ
Animal
の
Bird
サブクラスを作成します。さらに多様性を追加するために、コンストラクタ引数を使用してその動作を指定できるように、
Locomotion
を実装しません。
public class Bird extends Animal {
private boolean walks;
public Bird() {
super("bird");
}
public Bird(String name, boolean walks) {
super(name);
setWalks(walks);
}
public Bird(String name) {
super(name);
}
public boolean walks() {
return walks;
}
//standard setters and overridden methods
}
このクラスには3つのコンストラクタがあることを熟考して確認しましょう。
@Test
public void givenClass__whenGetsAllConstructors__thenCorrect() {
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Constructor<?>[]constructors = birdClass.getConstructors();
assertEquals(3, constructors.length);
}
次に、コンストラクタのパラメータクラス型を宣言された順序で渡すことによって、
Bird
クラスの各コンストラクタを取得します。
@Test
public void givenClass__whenGetsEachConstructorByParamTypes__thenCorrect(){
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Constructor<?> cons1 = birdClass.getConstructor();
Constructor<?> cons2 = birdClass.getConstructor(String.class);
Constructor<?> cons3 = birdClass.getConstructor(String.class, boolean.class);
}
指定された順序で指定されたパラメータ型を持つコンストラクタが存在しない場合、
NoSuchMethodException
が発生し、テストが自動的に失敗するため、アサーションは必要ありません。
最後のテストでは、実行時にパラメータを指定しながらオブジェクトをインスタンス化する方法を説明します。
@Test
public void givenClass__whenInstantiatesObjectsAtRuntime__thenCorrect() {
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Constructor<?> cons1 = birdClass.getConstructor();
Constructor<?> cons2 = birdClass.getConstructor(String.class);
Constructor<?> cons3 = birdClass.getConstructor(String.class,
boolean.class);
Bird bird1 = (Bird) cons1.newInstance();
Bird bird2 = (Bird) cons2.newInstance("Weaver bird");
Bird bird3 = (Bird) cons3.newInstance("dove", true);
assertEquals("bird", bird1.getName());
assertEquals("Weaver bird", bird2.getName());
assertEquals("dove", bird3.getName());
assertFalse(bird1.walks());
assertTrue(bird3.walks());
}
Constructor
classの
newInstance
メソッドを呼び出し、必要なパラメータを宣言された順序で渡すことによって、クラスオブジェクトをインスタンス化します。その後、結果を必要な型にキャストします。
bird1
の場合は、
Bird
コードから自動的に名前をbirdに設定するデフォルトのコンストラクタを使用し、それをテストで確認します。
次に、名前とテストのみを使用して
bird2
をインスタンス化します。最後の2つのアサーションで見られるように、移動動作をデフォルトのfalseに設定しない場合は、覚えておいてください。
7. フィールドを検査する
これまでは、フィールドの名前だけを調べてきました。このセクションでは、実行時に値を取得および設定する方法を示します。
実行時にクラスのフィールドを調べるために使用される2つの主な方法があります。
getFields()
および
getField(fieldName)
。
getFields()
メソッドは、問題のクラスのアクセス可能なすべてのパブリックフィールドを返します。クラスとすべてのスーパークラスの両方のすべてのパブリックフィールドが返されます。
たとえば、
Bird
クラスでこのメソッドを呼び出すと、
Bird
自体はパブリックフィールドを宣言しないため、スーパークラス
Animal
の
CATEGORY
フィールドのみが取得されます。
@Test
public void givenClass__whenGetsPublicFields__thenCorrect() {
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Field[]fields = birdClass.getFields();
assertEquals(1, fields.length);
assertEquals("CATEGORY", fields[0].getName());
}
このメソッドには
getField
という名前のバリアントもあります。これは、フィールドの名前を取得して1つの
Field
オブジェクトだけを返します。
@Test
public void givenClass__whenGetsPublicFieldByName__thenCorrect() {
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Field field = birdClass.getField("CATEGORY");
assertEquals("CATEGORY", field.getName());
}
スーパークラスで宣言され、子クラスで宣言されていないプライベートフィールドにアクセスすることはできません。これが、
name
フィールドにアクセスできない理由です。
ただし、
getDeclaredFields
メソッドを呼び出すことで、処理しているクラスで宣言されているプライベートフィールドを調べることができます。
@Test
public void givenClass__whenGetsDeclaredFields__thenCorrect(){
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Field[]fields = birdClass.getDeclaredFields();
assertEquals(1, fields.length);
assertEquals("walks", fields[0].getName());
}
フィールドの名前がわかっている場合は、他のバリアントも使用できます。
@Test
public void givenClass__whenGetsFieldsByName__thenCorrect() {
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Field field = birdClass.getDeclaredField("walks");
assertEquals("walks", field.getName());
}
フィールドの名前が間違っていたり、既存のフィールドに入力した場合は、
NoSuchFieldException
が返されます。
フィールドタイプは次のようになります。
@Test
public void givenClassField__whenGetsType__thenCorrect() {
Field field = Class.forName("com.baeldung.reflection.Bird")
.getDeclaredField("walks");
Class<?> fieldClass = field.getType();
assertEquals("boolean", fieldClass.getSimpleName());
}
次に、フィールド値にアクセスしてそれらを変更する方法を調べます。設定するだけでなく、フィールドの値を取得できるようにするには、まず
Field
オブジェクトの
setAccessible
メソッドを呼び出してアクセス可能に設定し、それにブール値の
true
を渡す必要があります。
@Test
public void givenClassField__whenSetsAndGetsValue__thenCorrect() {
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Bird bird = (Bird) birdClass.newInstance();
Field field = birdClass.getDeclaredField("walks");
field.setAccessible(true);
assertFalse(field.getBoolean(bird));
assertFalse(bird.walks());
field.set(bird, true);
assertTrue(field.getBoolean(bird));
assertTrue(bird.walks());
}
上記のテストでは、
walks
フィールドの値がtrueに設定される前に、その値がfalseであることを確認しています。
Field
オブジェクトを使用して、処理しているクラスのインスタンスと、そのオブジェクトにフィールドに含める新しい値を渡すことで値を設定および取得する方法に注目してください。
Field
オブジェクトに関して注意すべき重要なことの1つは、それが
public static
として宣言されている場合、それらを含むクラスのインスタンスは必要ないということです。そのようです:
@Test
public void givenClassField__whenGetsAndSetsWithNull__thenCorrect(){
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Field field = birdClass.getField("CATEGORY");
field.setAccessible(true);
assertEquals("domestic", field.get(null));
}
8検査方法
前の例では、メソッド名を調べるためだけにリフレクションを使用しました。
ただし、Javaリフレクションはそれよりも強力です。
Javaリフレクションを使用すると、コンストラクタの場合と同じように、
実行時
にメソッドを呼び出して、必要なパラメータを渡すことができます。同様に、それぞれのパラメータ型を指定することでオーバーロードされたメソッドを呼び出すこともできます。
フィールドと同じように、クラスメソッドを取得するために使用する2つの主要なメソッドがあります。
getMethods
メソッドは、クラスおよびスーパークラスのすべてのパブリックメソッドの配列を返します。
つまり、このメソッドを使用すると、
toString、hashCode
、
notifyAll
などの
java.lang.Object
クラスのパブリックメソッドを取得できます。
@Test
public void givenClass__whenGetsAllPublicMethods__thenCorrect(){
Class<?> birdClass = Class.forName("com.baeldung.java.reflection.Bird");
Method[]methods = birdClass.getMethods();
List<String> methodNames = getMethodNames(methods);
assertTrue(methodNames.containsAll(Arrays
.asList("equals", "notifyAll", "hashCode",
"walks", "eats", "toString")));
}
関心のあるクラスのパブリックメソッドだけを取得するには、
getDeclaredMethods
メソッドを使用する必要があります。
@Test
public void givenClass__whenGetsOnlyDeclaredMethods__thenCorrect(){
Class<?> birdClass = Class.forName("com.baeldung.java.reflection.Bird");
List<String> actualMethodNames
= getMethodNames(birdClass.getDeclaredMethods());
List<String> expectedMethodNames = Arrays
.asList("setWalks", "walks", "getSound", "eats");
assertEquals(expectedMethodNames.size(), actualMethodNames.size());
assertTrue(expectedMethodNames.containsAll(actualMethodNames));
assertTrue(actualMethodNames.containsAll(expectedMethodNames));
}
これらの各メソッドは、名前がわかっている単一の
Method
オブジェクトを返す特異なバリエーションを持っています。
@Test
public void givenMethodName__whenGetsMethod__thenCorrect() {
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Method walksMethod = birdClass.getDeclaredMethod("walks");
Method setWalksMethod = birdClass.getDeclaredMethod("setWalks", boolean.class);
assertFalse(walksMethod.isAccessible());
assertFalse(setWalksMethod.isAccessible());
walksMethod.setAccessible(true);
setWalksMethod.setAccessible(true);
assertTrue(walksMethod.isAccessible());
assertTrue(setWalksMethod.isAccessible());
}
個々のメソッドを取得し、それらがどのようなパラメータタイプを取るかを指定する方法に注目してください。パラメータ型をとらないものは、空の可変引数で取得され、単一の引数、メソッド名だけが残ります。
次に、実行時にメソッドを呼び出す方法を示します。
Bird
クラスの
walks
属性は
false
であることがデフォルトでわかっています。その
setWalks
メソッドを呼び出して
true
に設定します。
@Test
public void givenMethod__whenInvokes__thenCorrect() {
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Bird bird = (Bird) birdClass.newInstance();
Method setWalksMethod = birdClass.getDeclaredMethod("setWalks", boolean.class);
Method walksMethod = birdClass.getDeclaredMethod("walks");
boolean walks = (boolean) walksMethod.invoke(bird);
assertFalse(walks);
assertFalse(bird.walks());
setWalksMethod.invoke(bird, true);
boolean walks2 = (boolean) walksMethod.invoke(bird);
assertTrue(walks2);
assertTrue(bird.walks());
}
最初に
walks
メソッドを呼び出し、戻り型を適切なデータ型にキャストしてからその値を確認する方法に注目してください。その後、後で
setWalks
メソッドを呼び出してその値を変更し、もう一度テストします。
9結論
このチュートリアルでは、Java Reflection APIについて説明し、コンパイル時に内部の知識がなくても実行時にそれを使用してクラス、インタフェース、フィールド、およびメソッドを検査する方法を調べました。
このチュートリアルの完全なソースコードと例は私のhttps://github.com/eugenp/tutorials/tree/master/core-java-lang[Github]プロジェクトにあります。