1. 概要

Reflection は、コンピューターソフトウェアが実行時にその構造を検査する機能です。 Javaでは、 Java ReflectionAPIを使用してこれを実現します。 これにより、フィールド、メソッド、さらには内部クラスなどのクラスの要素をすべて実行時に検査できます。

このチュートリアルでは、プライベートフィールドと継承フィールドを含むJavaクラスのフィールドを取得する方法に焦点を当てます。

2. クラスからのフィールドの取得

まず、可視性に関係なく、クラスのフィールドを取得する方法を見てみましょう。 後で、継承されたフィールドを取得する方法も説明します。

2つのStringフィールドを持つPersonクラスの例から始めましょう:lastNamefirstName。 前者は保護された(後で役立ちます)であり、後者はプライベート:です。

public class Person {
    protected String lastName;
    private String firstName;
}

リフレクションを使用して、lastNameフィールドとfirstNameフィールドの両方を取得します。 これは、Class::getDeclaredFieldsメソッドを使用して実現します。 その名前が示すように、これはクラスの宣言されたすべてのフィールドを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クラスの継承されたフィールドを取得する方法を見てみましょう。

これを説明するために、Personを拡張するEmployeeという名前の2番目のクラスを、独自のフィールドで作成してみましょう。

public class Employee extends Person {
    public int employeeId;
}

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

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

もちろん、PersonクラスとEmployeeクラスの両方でgetDeclaredFields()メソッドを使用して、それらの結果を1つの配列にマージすることもできます。 しかし、スーパークラスを明示的に指定したくない場合はどうでしょうか。

この場合、 JavaReflectionAPIの別のメソッドであるClass::getSuperclassを利用できます。 これにより、別のクラスのスーパークラスが得られます。そのスーパークラスが何であるかを知る必要はありません。

Employee.classおよびEmployee.class.getSuperclass() getDeclaredFields()の結果を収集し、それらを1つの配列にマージしてみましょう。

@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つのフィールドを収集したことがわかります。

しかし、Personprivateフィールドは本当に継承されたフィールドですか? それほど多くはありません。 これは、package-privateフィールドでも同じです。 パブリックフィールドと保護されたフィールドのみが継承されたと見なされます。

3.2. publicおよびprotectedフィールドのフィルタリング

残念ながら、Java APIのメソッドでは、クラスとそのスーパークラスからpublicおよびprotectedフィールドを収集できません。 Class :: getFields メソッドは、クラスとそのスーパークラスのすべての public フィールドを返しますが、 protected フィールドは返さないため、目標に近づきます。

継承されたフィールドのみを取得する必要がある唯一の方法は、先ほど行ったように getDeclaredFields()メソッドを使用し、 Field ::getModifiersメソッドを使用してその結果をフィルター処理することです。 これは、現在のフィールドの修飾子を表すintを返します。 可能な各修飾子には、2^0から2^7までの2の累乗が割り当てられます。

たとえば、public2^ 0 であり、static2^3です。 したがって、 publicおよびstaticフィールドでgetModifiers()メソッドを呼び出すと、9が返されます。

次に、この値と特定の修飾子の値の間でビット単位およびを実行して、そのフィールドにその修飾子があるかどうかを確認できます。 操作が0以外のものを返す場合、修飾子が適用されます。それ以外の場合は適用されません。

Javaは、 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. 深いクラス階層での継承フィールドの取得

上記の例では、単一のクラス階層で作業しました。 より深いクラス階層があり、継承されたすべてのフィールドを収集したい場合、今何をしますか?

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;
}

このクラスは、Rewardという新しいフィールドを定義します。 すべての階層クラスが与えられると、このメソッドは次のフィールド定義を提供する必要があります: Person :: lastName、Employee ::employeeIdおよびMonthEmployee::Reward

MonthEmployeegetAllFields()メソッドを呼び出しましょう。

@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 ReflectionAPIを使用してJavaクラスのフィールドを取得する方法を説明しました。

最初に、クラスの宣言されたフィールドを取得する方法を学びました。 その後、スーパークラスフィールドを取得する方法も確認しました。 次に、パブリックおよび保護されていないフィールドを除外する方法を学びました。

最後に、これらすべてを適用して、複数のクラス階層の継承されたフィールドを収集する方法を確認しました。

いつものように、この記事の完全なコードは、GitHubから入手できます。