classe java.lang.reflect.Field

La classe Field fornisce informazioni e accesso dinamico a un singolo campo di una classe o di un'interfaccia. Field consente anche una conversione del tipo di ampliamento durante un'operazione di accesso get o set, ma genera un'eccezione IllegalArgumentException se si verifica un restringimento.

Per ottenere un oggetto Field , scriveremo prima una 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;
}

Ed ecco il nostro codice per lavorare con quella 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);
    }
}

È così che otteniamo l'elenco dei campi della nostra classe, con cui lavoreremo in seguito. Ecco il risultato:

[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.azienda.Persona.MAX_AGE, public static final int com.azienda.Persona.MIN_AGE]

Ora cerchiamo di capire cosa possiamo fare con questi dati. Parliamo dei metodi della classe Field :

Metodo Descrizione
getType() Restituisce un oggetto Class che identifica il tipo dichiarato del campo rappresentato da thisCampooggetto.
getAnnotatedType() Restituisce unTipo annotatooggetto che rappresenta l'uso di un tipo per specificare il tipo dichiarato del campo rappresentato da questo campo.
getGenericType() restituisce unTipooggetto che rappresenta il tipo dichiarato del campo rappresentato da thisCampooggetto.
getNome() Restituisce il nome del campo rappresentato da thisCampooggetto.
getModifiers() Restituisce un int che codifica i modificatori del linguaggio Java per il campo rappresentato da thisCampooggetto.
getAnnotations() Restituisce le annotazioni per questo campo. Se non ci sono annotazioni, restituisce un array vuoto.

getType(), getName() e getModifiers()

Possiamo usare il metodo getType() per ottenere il tipo del nostro campo. Scriviamo un metodo:


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

E otteniamo questo risultato:

class java.lang.String
int
booleano
class java.lang.String
int
int

Ora aggiungiamo un metodo alla nostra classe per ottenere il nome di un campo. Questo renderà più facile navigare nei campi della nostra 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()));
}

Ora il risultato è più comprensibile:

Tipo di campo - class java.lang.String
Nome campo - name

Tipo di campo - int
Nome campo - age

Tipo di campo - boolean
Nome campo - isMale

Tipo di campo - class java.lang.String
Nome campo - address

Tipo di campo - int
Nome campo - MAX_AGE

Tipo di campo - int
Nome campo - MIN_AGE

Grande! Modifichiamo ancora un po' il nostro metodo! Aggiungeremo qui i modificatori di accesso


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

E diamo un'occhiata a cosa restituisce e.getModifiers() . Questo metodo restituisce un int che ci consente di determinare lo stato dei modificatori di accesso del nostro campo. La classe Modifier contiene variabili statiche responsabili di ogni specifico modificatore del campo.

Avvolgiamo il nostro valore restituito in Modifier.toString() e otteniamo immediatamente il suo valore come testo:

Tipo di campo - class java.lang.String
Nome campo - name
Modificatori - private

Tipo di campo - int
Nome campo - age
Modificatori - private

Tipo di campo - boolean
Nome campo - isMale
Modificatori - public

Tipo di campo - class java.lang.String
Nome campo - address
Modificatori - protected

Tipo di campo - int
Nome campo - MAX_AGE
Modificatori - public static final

Tipo di campo - int
Nome campo - MIN_AGE
Modificatori - public static final

Ecco come appare senza Modifier.toString() :

Tipo di campo - class java.lang.String
Nome campo - name
Modificatori - 2

Tipo di campo - int
Nome campo - age
Modificatori - 2

Tipo di campo - boolean
Nome campo - isMale
Modificatori - 1

Tipo di campo - class java.lang.String
Nome campo - address
Modificatori - 4

Tipo di campo - int
Nome campo - MAX_AGE
Modificatori - 25

Tipo di campo - int
Nome campo - MIN_AGE
Modificatori - 25

metodi getAnnotations(), getAnnotatedType() e getGenericType()

Modifichiamo la classe Person per lavorare con questi metodi. Scriveremo la nostra annotazione e la applicheremo ai nostri campi. E aggiungeremo altri campi.

Creiamo due annotazioni. Passeremo il nome della variabile in Pig Latin a uno e useremo il secondo per gli elementi:


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

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

E cambieremo la nostra classe principale e la classe Persona :


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

È tempo di esaminare i risultati dei nostri metodi e capire a cosa servono:

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

getGenericType:
java.lang.Class<java.lang. Oggetto>
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")]
[@Nome(nome="\u0041\u006d\u0065\u002d\u006e\u0061\u0079")] [
]
  • getAnnotatedType restituisce l'annotazione per il campo specificato, se presente. Otteniamo l'annotazione per il campo e possiamo vederla perfettamente.

  • getGenericType consente di visualizzare correttamente un tipo parametrizzato.

  • getAnnotations restituisce le annotazioni applicate al nostro oggetto.

Questo è il modo in cui possiamo ottenere facilmente tutti i dati su ogni campo nella nostra classe, così come i suoi modificatori di accesso, annotazioni e tipi di dati.

classe java.lang.reflect.Method

Super! Abbiamo parlato dei campi della nostra classe. Ora è il momento di parlare dei metodi.

Per ottenere un oggetto Method , chiamiamo il metodo getMethod , passandogli il nome del nostro metodo. Questo è il modo di base per ottenere un oggetto Method :


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

Continueremo a lavorare con la nostra classe. Aggiungiamo i metodi getter e setter e hashCode, equals e 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);
    }
}

Ora prepariamo un set di metodi che esamineremo utilizzando la classe Method . Ecco un elenco dei metodi più importanti:

Metodo Descrizione
getNome() Restituisce il nome del metodo.
getModifiers() Restituisce il modificatore di accesso di questo metodo.
getReturnType() Restituisce il tipo restituito del metodo.
getGenericReturnType() Restituisce il tipo restituito del metodo, tenendo conto dei metodi generici.
getParameterTypes() Restituisce una matrice di parametri del metodo.
getGenericParameterTypes() Restituisce una matrice di parametri di metodo, tenendo conto dei metodi generici.
getExceptionTypes() Restituisce le eccezioni che il metodo può generare.
getGenericExceptionTypes() Restituisce le eccezioni che il metodo può generare, tenendo conto dei tipi con parametri.
getAnnotations() Restituisce le annotazioni per il metodo, incluse le annotazioni padre.
getDeclaredAnnotations() Restituisce le annotazioni per il metodo, escluse le annotazioni padre.

Per ottenere un array dei metodi della nostra classe, possiamo chiamare questo metodo:


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

Ci darà tutti i metodi della nostra classe.

getName() e getModifiers()

Possiamo usare getName per ottenere il nome di ciascun metodo:


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

Ora per ottenere i modificatori, scriviamo un metodo che utilizza getModifiers :


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

Ecco il nostro metodo principale :


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

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

Il nostro risultato:

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

Tutti i nostri metodi hanno il modificatore public , quindi l'ultimo metodo restituisce un array di quelli. Se modifichiamo il nostro codice, vedremo i nostri modificatori stessi:


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()

Questo metodo ci consente di ottenere il tipo di ritorno del metodo:


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

getGenericReturnType()

Diamo alla nostra classe Person un metodo che restituisce il tipo racchiuso in un tipo parametrizzato e proviamo a ottenere il suo valore di ritorno:


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

E aggiorneremo il nostro metodo principale:


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

Risultato del nostro metodo:

class java.lang.String
boolean
class java.lang.String
int
void
class java.lang.String
boolean
int
void
void
void
java.util.List<java.lang.String>

getParameterTypes() e getGenericParameterTypes()

Continuiamo a modificare la nostra classe Person , aggiungendo altri due metodi:


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

Il primo ci permetterà di ottenere i parametri dei nostri metodi e il secondo ci darà anche i tipi parametrizzati.


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

Accederemo solo a uno dei nostri metodi. Per accedere a un metodo con un nome specifico, chiamiamo getMethod e passiamo il nome e i parametri del metodo che vogliamo:


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

Eseguendo il nostro codice, vedremo come i metodi differiscono e cosa restituiscono:

interfaccia java.util.List
classe java.lang.String

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

getExceptionTypes() e getGenericExceptionTypes()

Possiamo utilizzare questi metodi per ottenere un array di eccezioni che il nostro metodo può generare, nonché eccezioni con tipi parametrizzati (se presenti). Usiamo un nuovo esempio che ha una classe statica nascosta:


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

    private void process() throws IOException {}
}

E chiameremo i metodi sulla nostra 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));
}

Ora possiamo vedere la nostra eccezione:

[classe java.io.IOException]

Ora parametrizziamo il tipo. Modificheremo la nostra classe principale:


private static class Processor<E extends IOException> {

    private void process() throws E {
    }
}

E il codice del metodo principale :


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

All'interno di questo metodo, abbiamo un oggetto TypeVariables , che è un'interfaccia padre generica per le variabili di tipo. E al suo interno, ora possiamo ottenere il parametro interno, vale a dire la nostra eccezione nidificata:

[E]
classe java.io.IOException

metodi getAnnotations() e getDeclaredAnnotations()

Continuiamo a utilizzare questa nuova classe e aggiungiamo un paio di annotazioni. Creeremo la nostra annotazione di annotazione :


@Retention(RetentionPolicy.RUNTIME)
@interface Annotation {

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

E applicalo al nostro metodo:


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

}

E ovviamente aggiungeremo un metodo per visualizzare tutte le nostre annotazioni:


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

Implementazione del nostro metodo principale :


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

L'output dello schermo risultante è:

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

Questo è il modo in cui possiamo ottenere le annotazioni che sono state applicate ai nostri metodi e il metodo getAnnotations ci consente di accedere anche alle annotazioni padre della classe.

Oggi abbiamo imparato come la riflessione può funzionare con metodi e campi e quali dati possiamo ottenere con essa!