Classe java.lang.reflect.Field

La classe Field fournit des informations et un accès dynamique à un seul champ d'une classe ou d'une interface. Field permet également une conversion de type d'élargissement lors d'une opération d'accès get ou set, mais lève une exception IllegalArgumentException si un rétrécissement se produit.

Pour obtenir un objet Field , nous allons d'abord écrire une classe :


public class Person {
    private String name;
    private int age;
    
    public boolean isMale;
    
    protected String address;
    
    public static final int MAX_AGE = 120;
    public static final int MIN_AGE = 0;
}

Et voici notre code pour travailler avec cette classe :


public class Main {
    public static void main(String[] args) {
        Field[] fields = Person.class.getDeclaredFields();
        List<Field> actualFields = getFieldNames(fields);
        System.out.println(actualFields);
    }

    static List<Field> getFieldNames(Field[] fields) {
        return List.of(fields);
    }
}

C'est ainsi que nous obtenons la liste des champs de notre classe, avec laquelle nous travaillerons plus tard. Voici le résultat :

[private java.lang.String com.company.Person.name, private int com.company.Person.age, public boolean com.company.Person.isMale, protected java.lang.String com.company.Person.address, public static final int com.company.Person.MAX_AGE, public static final int com.company.Person.MIN_AGE]

Voyons maintenant ce que nous pouvons faire avec ces données. Parlons des méthodes de la classe Field :

Méthode Description
getType() Renvoie un objet Class qui identifie le type déclaré du champ représenté par cetChampobjet.
getAnnotatedType() Renvoie unAnnotatedTypeobjet qui représente l'utilisation d'un type pour spécifier le type déclaré du champ représenté par ce champ.
getGenericType() Renvoie unTaperobjet qui représente le type déclaré du champ représenté par cetChampobjet.
obtenirNom() Renvoie le nom du champ représenté par thisChampobjet.
getModifiers() Renvoie un int encodant les modificateurs de langage Java pour le champ représenté par thisChampobjet.
getAnnotation() Renvoie les annotations pour ce champ. S'il n'y a pas d'annotations, il renvoie un tableau vide.

Méthodes getType(), getName() et getModifiers()

Nous pouvons utiliser la méthode getType() pour obtenir le type de notre champ. Écrivons une méthode :


static void printTypes(List<Field> fields){
      fields.forEach(e -> System.out.println(e.getType()));
  }

Et nous obtenons ce résultat :

classe java.lang.String
entier
booléen
classe java.lang.String
int
entier

Ajoutons maintenant une méthode à notre classe pour obtenir le nom d'un champ. Cela facilitera la navigation dans les champs de notre classe.


static void printTypesAndNames(List<Field> fields){
   fields.forEach(e -> System.out.printf("Field type - %s\nField name - %s\n\n", e.getType(), e.getName()));
}

Maintenant, le résultat est plus compréhensible :

Type de champ - classe java.lang.String
Nom du champ - name

Type de champ - int
Nom du champ - age

Type de champ - booléen
Nom du champ - isMale

Type de champ - classe java.lang.String
Nom du champ - address

Type de champ - int
Nom du champ - MAX_AGE

Type de champ - int
Nom du champ - MIN_AGE

Super! Modifions encore notre méthode ! Nous ajouterons des modificateurs d'accès ici


static void printFieldInfo(List<Field> fields){
   fields.forEach(e -> System.out.printf("Field type - %s\nField name - %s\nModifiers - %s\n\n", e.getType(), e.getName(), Modifier.toString(e.getModifiers())));
}

Et regardons ce que renvoie e.getModifiers() . Cette méthode renvoie un int qui nous permet de déterminer l'état des modificateurs d'accès de notre champ. La classe Modifier contient des variables statiques responsables de chaque modificateur spécifique du champ.

Enveloppons notre valeur de retour dans Modifier.toString() et obtenons immédiatement sa valeur sous forme de texte :

Type de champ - classe java.lang.String
Nom du champ - nom
Modificateurs - privé

Type de champ - int
Nom du champ - age
Modificateurs - privé

Type de champ - booléen
Nom du champ - isMale
Modificateurs - public

Type de champ - classe java.lang.String
Nom du champ - address
Modificateurs - protected

Type de champ - int
Nom du champ - MAX_AGE
Modificateurs - public static final

Type de champ - int
Nom du champ - MIN_AGE
Modificateurs - public static final

Voici à quoi cela ressemble sans Modifier.toString() :

Type de champ - classe java.lang.String
Nom du champ - nom
Modificateurs - 2

Type de champ - int
Nom du champ - age
Modificateurs - 2

Type de champ - booléen
Nom du champ - isMale
Modificateurs - 1

Type de champ - classe java.lang.String
Nom du champ - address
Modificateurs - 4

Type de champ - int
Nom de champ - MAX_AGE
Modificateurs - 25

Type de champ - int
Nom de champ - MIN_AGE
Modificateurs - 25

Méthodes getAnnotations(), getAnnotatedType() et getGenericType()

Modifions la classe Person pour travailler avec ces méthodes. Nous écrirons notre propre annotation et l'appliquerons à nos champs. Et nous ajouterons quelques champs supplémentaires.

Créons deux annotations. Nous passerons le nom de la variable en Pig Latin à un, et nous utiliserons le second pour les éléments :


@Target(value=ElementType.FIELD)
@Retention(value= RetentionPolicy.RUNTIME)
public @interface Name {
    String name();
}

@Target({ ElementType.TYPE_USE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Number {
}

Et nous allons changer notre classe principale et la classe Person :


public class Person {
    @Name(name = "Ame-nay")
    private String name;

    @Name(name = "User nicknames")
    List<String> nicknames;

    private final Class<Object> type;

    private int @Number[] number;

    public Person(Class<Object> type) {
        this.type = type;
    }
}

public static void main(String[] args) {
    Field[] fields = Person.class.getDeclaredFields();
    List<Field> actualFields = getFieldNames(fields);
    
    printAdditionalInfo(actualFields);
}

static void printAdditionalInfo(List<Field> fields) {
   System.out.println("\ngetAnnotatedType:");
   fields.forEach(e -> System.out.println(e.getAnnotatedType()));

   System.out.println("\ngetGenericType:");
   fields.forEach(e -> System.out.println(e.getGenericType()));

   System.out.println("\ngetAnnotations:");
   fields.forEach(e -> System.out.println(Arrays.toString(e.getAnnotations())));
}

Il est temps d'examiner les résultats de nos méthodes et de comprendre à quoi elles servent :

getAnnotatedType :
java.lang.Class<java.lang.Object>
java.util.List<java.lang.String>
java.lang.String
int @Number()[]

getGenericType :
java.lang.Class<java.lang. Objet>
classe java.util.List<java.lang.String> classe
java.lang.String
[I

getAnnotations :
[]
[@Name(name="\u0055\u0073\u0065\u0072\u0020\u006e\u0069\u0063 \u006b\u006e\u0061\u006d\u0065\u0073")]
[@Name(name="\u0041\u006d\u0065\u002d\u006e\u0061\u0079")] [
]
  • getAnnotatedType renvoie l'annotation pour le champ donné, le cas échéant. Nous obtenons l'annotation pour le champ et nous pouvons le voir parfaitement.

  • getGenericType permet d'afficher correctement un type paramétré.

  • getAnnotations renvoie les annotations appliquées à notre objet.

C'est ainsi que nous pouvons facilement obtenir toutes les données sur chaque champ de notre classe, ainsi que ses modificateurs d'accès, ses annotations et ses types de données.

Classe java.lang.reflect.Method

Super! Nous avons parlé des domaines de notre classe. Il est maintenant temps de parler des méthodes.

Pour obtenir un objet Method , nous appelons la méthode getMethod en lui passant le nom de notre méthode. Voici la méthode de base pour obtenir un objet Method :


Method getNameMethod =  Person.class.getMethod("getName");

Nous continuerons à travailler avec notre classe. Ajoutons des getters et des setters, et les méthodes hashCode, equals et toString :


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

    public boolean isMale;

    protected String address;

    public static final int MAX_AGE = 120;
    public static final int MIN_AGE = 0;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public boolean isMale() {
        return isMale;
    }

    public void setMale(boolean male) {
        isMale = male;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", isMale=" + isMale +
                ", address='" + address + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && isMale == person.isMale && Objects.equals(name, person.name) && Objects.equals(address, person.address);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, isMale, address);
    }
}

Préparons maintenant un ensemble de méthodes que nous allons examiner à l'aide de la classe Method . Voici une liste des méthodes les plus importantes :

Méthode Description
obtenirNom() Renvoie le nom de la méthode.
getModifiers() Renvoie le modificateur d'accès de cette méthode.
getReturnType() Renvoie le type de retour de la méthode.
getGenericReturnType() Renvoie le type de retour de la méthode, en tenant compte des méthodes génériques.
getParameterTypes() Renvoie un tableau de paramètres de méthode.
getGenericParameterTypes() Renvoie un tableau de paramètres de méthode, en tenant compte des méthodes génériques.
getExceptionTypes() Retourne les exceptions que la méthode peut lever.
getGenericExceptionTypes() Retourne les exceptions que la méthode peut lever, en tenant compte des types paramétrés.
getAnnotation() Renvoie les annotations de la méthode, y compris les annotations parent.
getDeclaredAnnotations() Renvoie les annotations de la méthode, à l'exclusion des annotations parentes.

Pour obtenir un tableau des méthodes de notre classe will, nous pouvons appeler cette méthode :


Method[] methods = Person.class.getDeclaredMethods();

Il nous donnera toutes les méthodes de notre classe.

Méthodes getName() et getModifiers()

Nous pouvons utiliser getName pour obtenir le nom de chaque méthode :


static List<String> getMethodsName(Method[] methods) {
    return Arrays.asList(methods)
            .stream()
            .map(Method::getName)
            .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
}

Maintenant, pour obtenir les modificateurs, écrivons une méthode qui utilise getModifiers :


static List<String> getModifiers(Method[] methods) {
    return Arrays
            .stream(methods)
            .map(Method::getModifiers)
            .map(String::valueOf)
            .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
}

Voici notre méthode principale :


public static void main(String[] args) {
    Method[] methods = Person.class.getDeclaredMethods();

    System.out.println(getMethodsName(methods));
    System.out.println(getModifiers(methods));
}

Notre résultat :

[getName, égal, toString, hashCode, setName, getAddress, isMale, getAge, setAge, setMale, setAddress]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

Toutes nos méthodes ont le modificateur public , donc la dernière méthode renvoie un tableau d'unités. Si nous modifions notre code, nous verrons nos modificateurs eux-mêmes :


public static void main(String[] args) {
    Method[] methods = Person.class.getDeclaredMethods();

    System.out.println(getMethodsName(methods));
    System.out.println(modifyModifiers(getModifiers(methods)));
}
[getName, equals, toString, hashCode, setName, getAddress, isMale, getAge, setAge, setMale, setAddress] [
public, public, public, public, public, public, public, public, public, public, public]

getReturnedType()

Cette méthode nous permet d'obtenir le type de retour de la méthode :


static void getReturnedType(Method[] methods) {
    Arrays.stream(methods)
            .map(Method::getReturnType)
            .forEach(System.out::println);
}
classe java.lang.String
booléen
classe java.lang.String
int
void
classe java.lang.String
booléen
int
void
void
void

getGenericReturnType()

Donnons à notre classe Person une méthode qui renvoie le type enveloppé dans un type paramétré, et essayons d'obtenir sa valeur de retour :


public List<String> someMethod() {
    // Very useful and important method
    return null;
}

Et nous mettrons à jour notre méthode principale :


static void getGenericReturnType(Method[] methods) {
    Arrays.stream(methods)
            .map(Method::getGenericReturnType)
            .forEach(System.out::println);
}

Résultat de notre méthode :

classe java.lang.String
booléen
classe java.lang.String
int
void
classe java.lang.String
booléen
int
void
void
void
java.util.List<java.lang.String>

Méthodes getParameterTypes() et getGenericParameterTypes()

Nous continuons à modifier notre classe Person , en ajoutant deux méthodes supplémentaires :


public List<String> someMethod(List<String> list, String s) {
    // Very useful and important method
    return null;
}

Le premier nous permettra d'obtenir les paramètres de nos méthodes, et le second nous donnera également des types paramétrés.


static void getParameterTypes(Method[] methods) {
    Class<?>[] types = method.getParameterTypes();
        for (Class<?> type : types) {
            System.out.println(type);
        }
}

static void getGenericParameterTypes(Method[] methods) {
   Type[] types = method.getGenericParameterTypes();
        for (Type type : types) {
            System.out.println(type);
        }
}

Nous n'accéderons qu'à une seule de nos méthodes. Pour accéder à une méthode par un nom spécifique, nous appelons getMethod et transmettons le nom et les paramètres de la méthode souhaitée :


public static void main(String[] args) throws NoSuchMethodException {
    Method currentMethod = Person.class.getMethod("someMethod", List.class, String.class);

    getParameterTypes(currentMethod);
    System.out.println();
    getGenericParameterTypes(currentMethod);
}

En exécutant notre code, nous verrons en quoi les méthodes diffèrent et ce qu'elles renvoient :

interface java.util.List
classe java.lang.String

java.util.List<java.lang.String>
classe java.lang.String

Méthodes getExceptionTypes() et getGenericExceptionTypes()

Nous pouvons utiliser ces méthodes pour obtenir un tableau d'exceptions que notre méthode peut lever, ainsi que des exceptions avec des types paramétrés (le cas échéant). Utilisons un nouvel exemple qui a une classe statique cachée :


private static class Processor {
    private void init() {}

    private void process() throws IOException {}
}

Et nous appellerons des méthodes sur notre classe Processor :


public static void main(String... args) throws NoSuchMethodException {
    Method method = Processor.class.getDeclaredMethod("process");
    Type[] type = method.getExceptionTypes();
    System.out.println(Arrays.toString(type));
}

Maintenant, nous pouvons voir notre exception :

[classe java.io.IOException]

Paramétrons maintenant le type. Nous allons modifier notre classe principale :


private static class Processor<E extends IOException> {

    private void process() throws E {
    }
}

Et le code de la méthode main :


public static void main(String... args) throws NoSuchMethodException {
    Method m = Processor.class.getDeclaredMethod("process");
    Type[] t = m.getGenericExceptionTypes();
    System.out.println(Arrays.toString(t));

    for (Type type : t) {
        if (type instanceof TypeVariable) {
            for (Type type1 : ((TypeVariable) type).getBounds()) {
                System.out.println(type1);
            }
        }
    }
}

Dans cette méthode, nous avons un objet TypeVariables , qui est une interface parente générique pour les variables de type. Et à l'intérieur de cela, nous pouvons maintenant obtenir le paramètre interne, à savoir notre exception imbriquée :

[E]
classe java.io.IOException

Méthodes getAnnotations() et getDeclaredAnnotations()

Continuons à utiliser cette nouvelle classe et ajoutons-y quelques annotations. Nous allons créer notre propre annotation Annotation :


@Retention(RetentionPolicy.RUNTIME)
@interface Annotation {

    public String key();
    public String value();
}

Et appliquez-le à notre méthode :


@Annotation(key = "key", value = "value")
private void process() throws E{

}

Et bien sûr, nous ajouterons une méthode pour afficher toutes nos annotations :


static void getMethodAnnotations(Class<?> clazz) {
    Method[] methods = clazz.getDeclaredMethods();
    for (Method method : methods) {
        System.out.println(method.getName());
        System.out.println(Arrays.toString(method.getAnnotations()));
        System.out.println();
    }
}

Mise en œuvre de notre méthode principale :


public static void main(String... args) {
    Class clazz = Processor.class;
    getMethodAnnotations(clazz);
}

La sortie d'écran résultante est :

processus
[@com.company.Main&Annotation(key="key", value="value")]

C'est ainsi que nous pouvons obtenir les annotations qui ont été appliquées à nos méthodes, et la méthode getAnnotations nous permet également d'accéder aux annotations parentes de la classe.

Aujourd'hui, nous nous sommes familiarisés avec la façon dont la réflexion peut fonctionner avec des méthodes et des domaines, et quelles données nous pouvons obtenir avec !