リフレクションを使用してJavaクラスからフィールドを取得する

1. 概要

link:/java-reflection[Reflection]は、コンピューターソフトウェアが実行時にその構造を検査する機能です。 Javaでは、Java Reflection APIを使用してこれを実現します。 これにより、フィールド、メソッド、さらには内部クラスなどのクラスの要素をすべて実行時に検査できます。
このチュートリアルでは、プライベートフィールドや継承フィールドなど、Javaクラスのフィールドを取得する方法に焦点を当てます。

2. クラスからフィールドを取得する

最初に、可視性に関係なく、クラスのフィールドを取得する方法を見てみましょう。 後で、継承されたフィールドを取得する方法についても説明します。
2つの_String_フィールドを持つ__Person_クラスの例から始めましょう:_lastName_と_firstName_。 前者は_protected_(後で役立ちます)で、後者は_private:_です
public class Person {
    protected String lastName;
    private String firstName;
}
リフレクションを使用して、_lastName_フィールドと_firstName_フィールドの両方を取得します。 * _Class

getDeclaredFields_メソッドを使用してこれを実現します。 名前が示すように、これはクラスのすべての_declared_フィールドをa__Field_配列の形式で返します:*

public class PersonAndEmployeeReflectionUnitTest {

    /* ... constants ... */

    @Test
    public void givenPersonClass_whenGetDeclaredFields_thenTwoFields() {
        Field[] allFields = Person.class.getDeclaredFields();

        assertEquals(2, allFields.length);

        assertTrue(Arrays.stream(allFields).anyMatch(field ->
          field.getName().equals(LAST_NAME_FIELD)
            && field.getType().equals(String.class))
        );
        assertTrue(Arrays.stream(allFields).anyMatch(field ->
          field.getName().equals(FIRST_NAME_FIELD)
            && field.getType().equals(String.class))
        );
    }

}
ご覧のとおり、_Person_クラスの2つのフィールドを取得します。 _Person_クラスのフィールド定義に一致する名前とタイプを確認します。

3. 継承されたフィールドの取得

Javaクラスの継承フィールドを取得する方法を見てみましょう。
これを説明するために、独自のフィールドを持つ_Employee_ extends _Person_という名前の2番目のクラスを作成しましょう。
public class Employee extends Person {
    public int employeeId;
}

3.1. 単純なクラス階層での継承フィールドの取得

  • Employee.class.getDeclaredFields()_を使用すると、_employeeId_フィールドのみが返されます*。このメソッドはスーパークラスで宣言されたフィールドを返さないためです。 継承されたフィールドも取得するには、_Person superclassのフィールドも取得する必要があります。

    もちろん、__ Person __と__Employee ___classesの両方で_getDeclaredFields()_メソッドを使用し、その結果を単一の配列にマージできます。 しかし、スーパークラスを明示的に指定したくない場合はどうでしょうか?
    この場合、* Java Reflection APIの別のメソッド:Class

    getSuperclass *を使用できます。 これにより、別のクラスのスーパークラスが得られますが、そのスーパークラスが何であるかを知る必要はありません。

    _Employee.class_および_Employee.class.getSuperclass()_で_getDeclaredFields()_の結果を収集し、それらを単一の配列にマージします。
@Test
public void givenEmployeeClass_whenGetDeclaredFieldsOnBothClasses_thenThreeFields() {
    Field[] personFields = Employee.class.getSuperclass().getDeclaredFields();
    Field[] employeeFields = Employee.class.getDeclaredFields();
    Field[] allFields = new Field[employeeFields.length + personFields.length];
    Arrays.setAll(allFields, i ->
      (i < personFields.length ? personFields[i] : employeeFields[i - personFields.length]));

    assertEquals(3, allFields.length);

    Field lastNameField = allFields[0];
    assertEquals(LAST_NAME_FIELD, lastNameField.getName());
    assertEquals(String.class, lastNameField.getType());

    Field firstNameField = allFields[1];
    assertEquals(FIRST_NAME_FIELD, firstNameField.getName());
    assertEquals(String.class, firstNameField.getType());

    Field employeeIdField = allFields[2];
    assertEquals(EMPLOYEE_ID_FIELD, employeeIdField.getName());
    assertEquals(int.class, employeeIdField.getType());
}
ここで、__Person ___の2つのフィールドと_Employee_の1つのフィールドを収集したことがわかります。
しかし、_Person_の_private_フィールドは本当に継承されたフィールドですか? そんなにありません。 _package-private_フィールドでも同じです。 * _public_および_protected_フィールドのみが継承されたと見なされます。*

3.2. _public_および_protected_フィールドのフィルタリング

残念ながら、Java APIのメソッドでは、クラスとそのスーパークラスからpublic _ protected_フィールドを収集できません。 _Class

getFields_メソッドは、クラスとそのスーパークラスのすべての_public_フィールドを返しますが、_protected_フィールドは返しません。

継承されたフィールドのみを取得する必要がある唯一の方法は、先ほどと同様に_getDeclaredFields()メソッドを使用し、_ Field

getModifiers _ methodを使用して結果をフィルタリングすることです。 これは、現在のフィールドの修飾子を表す_int_を返します。 可能な各修飾子には、_2 ^ 0_と_2 ^ 7_の間の2のべき乗が割り当てられます。

たとえば、_public_は_2 ^ 0_であり、_static_は_2 ^ 3_です。 したがって、a __public __and _static_フィールドで_getModifiers()_ methodを呼び出すと、9が返されます。
次に、この値と特定の修飾子の値の間で_bitwise and_を実行して、そのフィールドにその修飾子があるかどうかを確認できます。 操作が0以外を返す場合、修飾子が適用されます。それ以外の場合は適用されません。
Javaは、re_getModifiers()_によって返される値に修飾子が存在するかどうかを確認するユーティリティクラスを提供するので、幸運です。 *この例では、_isPublic()_メソッドと_isProtected()_メソッドを使用して、継承されたフィールドのみを収集しましょう。*
List<Field> personFields = Arrays.stream(Employee.class.getSuperclass().getDeclaredFields())
  .filter(f -> Modifier.isPublic(f.getModifiers()) || Modifier.isProtected(f.getModifiers()))
  .collect(Collectors.toList());

assertEquals(1, personFields.size());

assertTrue(personFields.stream().anyMatch(field ->
  field.getName().equals(LAST_NAME_FIELD)
    && field.getType().equals(String.class))
);
ご覧のとおり、結果には_private_フィールドが含まれていません。

3.3. 深いクラス階層での継承フィールドの取得

上記の例では、単一のクラス階層で作業しました。 より深いクラス階層があり、継承されたすべてのフィールドを収集したい場合、今何をしますか?
class_Employee_のサブクラスまたは_Personのスーパークラス– __がある場合、階層全体のフィールドを取得するには、すべてのスーパークラスをチェックする必要があります。
階層を介して実行されるユーティリティメソッドを作成し、完全な結果を構築することで、これを実現できます。
List<Field> getAllFields(Class clazz) {
    if (clazz == null) {
        return Collections.emptyList();
    }

    List<Field> result = new ArrayList<>(getAllFields(clazz.getSuperclass()));
    List<Field> filteredFields = Arrays.stream(clazz.getDeclaredFields())
      .filter(f -> Modifier.isPublic(f.getModifiers()) || Modifier.isProtected(f.getModifiers()))
      .collect(Collectors.toList());
    result.addAll(filteredFields);
    return result;
}
*この再帰的メソッドは、クラス階層を介して_public_および_protected_フィールドを検索し、_List_で見つかったすべてを返します。*
新しい_MonthEmployee_クラスを少しテストして、_Employee_を拡張して説明しましょう:
public class MonthEmployee extends Employee {
    protected double reward;
}
このクラスは、新しいフィールド– defines_reward_を定義します。 *すべての階層クラスが与えられると、このメソッドは次のフィールド*定義を提供する必要があります:P_Person

lastName、Employee :: employeeId_、および_MonthEmployee :: reward_。

_MonthEmployee_で_getAllFields()_メソッドを呼び出しましょう:
@Test
public void givenMonthEmployeeClass_whenGetAllFields_thenThreeFields() {
    List<Field> allFields = getAllFields(MonthEmployee.class);

    assertEquals(3, allFields.size());

    assertTrue(allFields.stream().anyMatch(field ->
      field.getName().equals(LAST_NAME_FIELD)
        && field.getType().equals(String.class))
    );
    assertTrue(allFields.stream().anyMatch(field ->
      field.getName().equals(EMPLOYEE_ID_FIELD)
        && field.getType().equals(int.class))
    );
    assertTrue(allFields.stream().anyMatch(field ->
      field.getName().equals(MONTH_EMPLOYEE_REWARD_FIELD)
        && field.getType().equals(double.class))
    );
}
予想どおり、すべての_public_および_protected_フィールドを収集します。

4. 結論

この記事では、_Java Reflection API_を使用してJavaクラスのフィールドを取得する方法を説明しました。
最初に、クラスの宣言されたフィールドを取得する方法を学びました。 その後、スーパークラスフィールドを取得する方法も確認しました。 次に、非パブリックフィールドと非保護フィールドを除外する方法を学びました。
最後に、このすべてを適用して、複数のクラス階層の継承フィールドを収集する方法を見ました。
いつものように、この記事の完全なコードはhttps://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-reflection[GitHubで]から入手できます。