リフレクション API は何のためにあるのでしょうか?

Java のリフレクション メカニズムを使用すると、開発者は名前を知らなくても、実行時にクラス、インターフェイス、フィールド、メソッドを変更したり、それらに関する情報を取得したりできます。

Reflection API を使用すると、新しいオブジェクトの作成、メソッドの呼び出し、フィールド値の取得または設定も可能になります。

リフレクションを使用してできることすべてのリストを作成してみましょう。

  • オブジェクトのクラスを識別/決定する
  • クラス修飾子、フィールド、メソッド、定数、コンストラクター、スーパークラスに関する情報を取得します。
  • 実装されたインターフェイスにどのメソッドが属しているかを調べる
  • プログラムが実行されるまでクラス名が不明なクラスのインスタンスを作成する
  • インスタンスフィールドの値を名前で取得および設定します
  • インスタンスメソッドを名前で呼び出す

最新の Java テクノロジのほとんどすべてがリフレクションを使用します。これは、今日の Java / Java EE フレームワークとライブラリのほとんどの基礎となっています。次に例を示します。

  • Web アプリケーションを構築するためのSpringフレームワーク
  • JUnitテスト フレームワーク

これまでにこれらのメカニズムに遭遇したことがない場合は、おそらくなぜこれが必要なのか疑問に思うでしょう。答えは非常にシンプルですが、非常に曖昧でもあります。リフレクションにより、アプリケーションとコードをカスタマイズする柔軟性と能力が劇的に向上します。

しかし、必ず長所と短所があります。そこで、いくつかの短所を挙げてみましょう。

  • アプリケーションのセキュリティの違反。リフレクションにより、アクセスすべきではないコードにアクセスできます (カプセル化の違反)。
  • セキュリティ制限。リフレクションには、セキュリティ マネージャーを実行しているシステムでは利用できない実行時権限が必要です。
  • 低性能。Java のリフレクションは、クラスパスをスキャンしてロードするクラスを見つけることにより、型を動的に決定します。これにより、プログラムのパフォーマンスが低下します。
  • 維持が難しい。リフレクションを使用するコードは、読み取りやデバッグが困難です。柔軟性が低く、保守が困難です。

Reflection API を使用したクラスの操作

すべてのリフレクション操作はjava.lang.Classオブジェクトで始まります。オブジェクトのタイプごとに、 java.lang.Classの不変インスタンスが作成されます。オブジェクトのプロパティの取得、新しいオブジェクトの作成、およびメソッドの呼び出しのためのメソッドが提供されます。

java.lang.Classを操作するための基本的なメソッドのリストを見てみましょう。

方法 アクション
文字列 getName(); クラスの名前を返します
int getModifiers(); アクセス修飾子を返します
パッケージ getPackage(); パッケージに関する情報を返します
クラス getSuperclass(); 親クラスに関する情報を返します。
Class[] getInterfaces(); インターフェイスの配列を返します
Constructor[] getConstructors(); クラスコンストラクターに関する情報を返します。
フィールド[] getFields(); クラスのフィールドを返します
フィールド getField(String fieldName); クラスの特定のフィールドを名前で返します
メソッド[] getMethods(); メソッドの配列を返します

これらは、クラス、インターフェイス、フィールド、メソッドに関するデータを取得するための最も重要なメソッドです。フィールド値を取得または設定したり、プライベートフィールドにアクセスしたりできるメソッドもあります。もう少し後で見てみましょう。

ここでは、 java.lang.Class自体の取得について説明しますこれを行うには 3 つの方法があります。

1. Class.forNameの使用

実行中のアプリケーションでは、forName(String className)メソッドを使用してクラスを取得する必要があります。

このコードは、リフレクションを使用してクラスを作成する方法を示しています。操作できるPersonクラスを作成しましょう。

package com.company;

public class Person {
    private int age;
    private String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

例の 2 番目の部分は、リフレクションを使用するコードです。

public class TestReflection {
    public static void main(String[] args) {
        try {
            Class<?> aClass = Class.forName("com.company.Person");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

このアプローチは、クラスの完全な名前がわかっている場合に可能です。次に、静的なClass.forName()メソッドを使用して、対応するクラスを取得できます。このメソッドはプリミティブ型には使用できません。

2. .classの使用

型が利用可能であるが、そのインスタンスがない場合は、型名に.classを追加することでクラスを取得できます。これは、プリミティブ型のクラスを取得する最も簡単な方法です。

Class aClass = Person.class;

3. .getClass() の使用

オブジェクトが使用可能な場合、クラスを取得する最も簡単な方法は、object.getClass()を呼び出すことです。

Person person = new Person();
Class aClass = person.getClass();

最後の 2 つのアプローチの違いは何ですか?

コーディング時にどのクラス オブジェクトに興味があるかがわかっている場合は、A.class を使用します。使用可能なインスタンスがない場合は、.classを使用する必要があります。

クラスのメソッドを取得する

クラスのメソッドを返すメソッド、getDeclaredMethods()getMethods()を見てみましょう。

getDeclaredMethods()は、Class オブジェクトによって表されるクラスまたはインターフェイスのすべての宣言されたメソッドの Method オブジェクトを含む配列を返します。これには、パブリック、プライベート、デフォルト、および保護されたメソッドが含まれますが、継承されたメソッドは含まれませ

getMethods() は、 Class オブジェクトによって表されるクラスまたはインターフェイスのすべてのパブリック メソッド (クラスまたはインターフェイスによって宣言されたメソッド、およびスーパークラスおよびスーパーインターフェイスから継承されたメソッド) の Method オブジェクトを含む配列を返します

それぞれがどのように機能するかを見てみましょう。

getDeclaredMethods()から始めましょう。2 つのメソッドの違いを再度理解できるように、以下では抽象Numbersクラスを使用します。Method配列をList<String>に変換する静的メソッドを作成しましょう。

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class TestReflection {
    public static void main(String[] args) {
        final Method[] declaredMethods = Number.class.getDeclaredMethods();
        List<String> actualMethodNames = getMethodNames(declaredMethods);
        actualMethodNames.forEach(System.out::println);
    }

    private static List<String> getMethodNames(Method[] methods) {
        return Arrays.stream(methods)
                .map(Method::getName)
                .collect(Collectors.toList());
    }
}

このコードを実行した結果は次のとおりです。

byteValue
shortValue
intValue
longValue
float floatValue;
doubleValue

これらはNumberクラス内で宣言されたメソッドです。getMethods()は何を返しますか? 例の 2 行を変更してみましょう。

final Method[] methods = Number.class.getMethods();
List<String> actualMethodNames = getMethodNames(methods);

これを行うと、次の一連のメソッドが表示されます。

byteValue
shortValue
intValue
longValue
float floatValue;
doubleValue
wait
wait
wait は toString hashCode getClass 通知 notifyAll
に等しい




すべてのクラスはObjectを継承するため、このメソッドはObjectクラスのパブリック メソッドも返します

クラスのフィールドを取得する

getFieldsメソッドgetDeclaredFieldsメソッドは、クラスのフィールドを取得するために使用されます。例として、LocalDateTimeクラスを見てみましょう。コードを書き直します。

import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class TestReflection {
    public static void main(String[] args) {
        final Field[] declaredFields = LocalDateTime.class.getDeclaredFields();
        List<String> actualFieldNames = getFieldNames(declaredFields);
        actualFieldNames.forEach(System.out::println);
    }

    private static List<String> getFieldNames(Field[] fields) {
        return Arrays.stream(fields)
                .map(Field::getName)
                .collect(Collectors.toList());
    }
}

このコードを実行した結果、LocalDateTime クラスに含まれるフィールドのセットを取得します。

MIN
MAXserialVersionUID 日付 時刻
_

前回のメソッドの検討と同様に、コードを少し変更するとどうなるかを見てみましょう。

final Field[] fields = LocalDateTime.class.getFields();
List<String> actualFieldNames = getFieldNames(fields);

出力:

最小
最大

次に、これらの方法の違いを見てみましょう。

getDeclaredFieldsメソッドは、このメソッドで表されるクラスまたはインターフェイスによって宣言されたすべてのフィールドのFieldオブジェクトの配列を返します。クラス物体。

getFieldsメソッドは、クラスまたはインターフェイスのすべてのパブリック フィールドFieldオブジェクトの配列を返します。クラス物体。

次に、 LocalDateTimeの内部を見てみましょう

クラスの最小マックスフィールドはパブリックです。つまり、getFieldsメソッドを通じて表示されます。対照的に、日にち時間シリアルバージョンUIDメソッドにはprivate修飾子が付いています。つまり、メソッドはgetFieldsメソッドでは表示されませんが、 getDeclaredFieldsを使用して取得できます。これは、プライベートフィールドの Field オブジェクトにアクセスする方法です。

その他のメソッドの説明

ここで、 Classクラスのいくつかのメソッドについて説明します

方法 アクション
修飾子を取得する クラスの修飾子を取得する
getパッケージ クラスを含むパッケージの取得
スーパークラスを取得 親クラスの取得
getInterfaces クラスによって実装されたインターフェイスの配列を取得する
getName 完全修飾クラス名の取得
getSimpleName クラス名の取得

getModifiers()

修飾子には、クラス物体。

修飾子は、 publicstaticinterfaceなどのキーワードです。 getModifiers()メソッドを使用して修飾子を取得します。

Class<Person> personClass = Person.class;
int classModifiers = personClass.getModifiers();

このコードは、整数ビットフィールドである変数。各アクセス修飾子は、対応するビットをセットまたはクリアすることでオンまたはオフにできます。java.lang.reflect.Modifierクラスのメソッドを使用して修飾子を確認できます。

import com.company.Person;
import java.lang.reflect.Modifier;

public class TestReflection {
    public static void main(String[] args) {
        Class<Person> personClass = Person.class;
        int classModifiers = personClass.getModifiers();

        boolean isPublic = Modifier.isPublic(classModifiers);
        boolean isStatic = Modifier.isStatic(classModifiers);
        boolean isFinal = Modifier.isFinal(classModifiers);
        boolean isAbstract = Modifier.isAbstract(classModifiers);
        boolean isInterface = Modifier.isInterface(classModifiers);

        System.out.printf("Class modifiers: %d%n", classModifiers);
        System.out.printf("Is public: %b%n", isPublic);
        System.out.printf("Is static: %b%n", isStatic);
        System.out.printf("Is final: %b%n", isFinal);
        System.out.printf("Is abstract: %b%n", isAbstract);
        System.out.printf("Is interface: %b%n", isInterface);
    }
}

パーソンの宣言がどのようになっているかを思い出してください。

public class Person {}

次の出力が得られます。

クラス修飾子: 1
パブリックである: true
静的である: false
最終的である: false
抽象的である: false
インターフェイスである: false

クラスを抽象化すると、次のようになります。

public abstract class Person {}

そしてこの出力:

クラス修飾子: 1025
パブリックである: true
静的である: false
最終的である: false
抽象的である: true
インターフェイスである: false

アクセス修飾子を変更しました。これは、 Modifierクラスの静的メソッドを通じて返されるデータも変更したことを意味します

getPackage()

クラスだけがわかっていれば、そのパッケージに関する情報を取得できます。

Class<Person> personClass = Person.class;
final Package aPackage = personClass.getPackage();
System.out.println(aPackage.getName());

getスーパークラス()

Class オブジェクトがある場合は、その親クラスにアクセスできます。

public static void main(String[] args) {
    Class<Person> personClass = Person.class;
    final Class<? super Person> superclass = personClass.getSuperclass();
    System.out.println(superclass);
}

よく知られているオブジェクトクラスを取得します。

class java.lang.Object

ただし、クラスに別の親クラスがある場合は、代わりにそれが表示されます。

package com.company;

class Human {
    // Some info
}

public class Person extends Human {
    private int age;
    private String name;

    // Some info
}

ここで親クラスを取得します。

class com.company.Human

getInterfaces()

クラスによって実装されたインターフェイスのリストを取得する方法は次のとおりです。

public static void main(String[] args) {
    Class<Person> personClass = Person.class;
    final Class<?>[] interfaces = personClass.getInterfaces();
    System.out.println(Arrays.toString(interfaces));
}

Personクラスを変更することを忘れないでください

public class Person implements Serializable {}

出力:

[インターフェースjava.io.Serializable]

クラスは多くのインターフェイスを実装できます。そのため、次の配列が得られます。クラスオブジェクト。Java Reflection API では、インターフェイスは次のようにも表されます。クラスオブジェクト。

注意:このメソッドは、親クラスではなく、指定されたクラスによって実装されたインターフェイスのみを返します。クラスによって実装されたインターフェイスの完全なリストを取得するには、現在のクラスと、継承チェーンの上のすべての祖先の両方を参照する必要があります。

getName() & getSimpleName() & getCanonicalName()

プリミティブ、ネストされたクラス、匿名クラス、およびStringクラスを含む例を書いてみましょう

public class TestReflection {
    public static void main(String[] args) {
        printNamesForClass(int.class, "int class (primitive)");
        printNamesForClass(String.class, "String.class (ordinary class)");
        printNamesForClass(java.util.HashMap.SimpleEntry.class,
                "java.util.HashMap.SimpleEntry.class (nested class)");
        printNamesForClass(new java.io.Serializable() {
                }.getClass(),
                "new java.io.Serializable(){}.getClass() (anonymous inner class)");
    }

    private static void printNamesForClass(final Class<?> clazz, final String label) {
        System.out.printf("%s:%n", label);
        System.out.printf("\tgetName()):\t%s%n", clazz.getName());
        System.out.printf("\tgetCanonicalName()):\t%s%n", clazz.getCanonicalName());
        System.out.printf("\tgetSimpleName()):\t%s%n", clazz.getSimpleName());
        System.out.printf("\tgetTypeName():\t%s%n%n", clazz.getTypeName());
    }
}

私たちのプログラムの結果:

int クラス (プリミティブ):
getName()): int
getCanonicalName()): int
getSimpleName()): int
getTypeName(): int

String.class (通常のクラス):
getName()): java.lang.String
getCanonicalName() ): java.lang.String
getSimpleName()): String
getTypeName(): java.lang.String

java.util.HashMap.SimpleEntry.class (ネストされたクラス):
getName()): java.util.AbstractMap$SimpleEntry
getCanonicalName( )): java.util.AbstractMap.SimpleEntry
getSimpleName()): SimpleEntry
getTypeName(): java.util.AbstractMap$SimpleEntry

新しい java.io.Serializable(){}.getClass() (匿名内部クラス):
getName() ): TestReflection$1
getCanonicalName()): null
getSimpleName()):
getTypeName(): TestReflection$1

次に、プログラムの出力を分析しましょう。

  • getName() はエンティティの名前を返します。

  • getCanonicalName() は、Java 言語仕様で定義されている基本クラスの正規名を返します。基本クラスに正規名がない場合 (つまり、ローカル クラス、匿名クラス、または要素型に正規名がない配列の場合)、null を返します。

  • getSimpleName() は、ソース コードで指定されている基本クラスの単純名を返します。基本クラスが匿名の場合は空の文字列を返します。

  • getTypeName() は、この型の名前の情報文字列を返します。