clase java.lang.reflect.Field

La clase Field proporciona información y acceso dinámico a un solo campo de una clase o interfaz. El campo también permite una conversión de tipo de ampliación durante una operación de acceso de obtención o configuración, pero genera una IllegalArgumentException si se produce una limitación.

Para obtener un objeto Field , primero escribiremos una clase:


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

Y aquí está nuestro código para trabajar con esa clase:


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

Así obtenemos la lista de los campos de nuestra clase, con los que trabajaremos más adelante. Aquí está el resultado:

[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]

Ahora veamos qué podemos hacer con estos datos. Hablemos de los métodos de la clase Field :

Método Descripción
obtenerTipo() Devuelve un objeto Class que identifica el tipo declarado del campo representado por este Field objeto.
obtenerTipoAnotado() Devuelve un Tipo anotado objeto que representa el uso de un tipo para especificar el tipo declarado del campo representado por este Campo.
getGenericType() Devuelve un Type objeto que representa el tipo declarado del campo representado por este Field objeto.
obtenerNombre() Devuelve el nombre del campo representado por este Field objeto.
obtenerModificadores() Devuelve un int que codifica los modificadores del lenguaje Java para el campo representado por este Field objeto.
obtener anotaciones() Devuelve las anotaciones para este campo. Si no hay anotaciones, devuelve una matriz vacía.

métodos getType(), getName() y getModifiers()

Podemos usar el método getType() para obtener el tipo de nuestro campo. Escribamos un método:


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

Y obtenemos este resultado:

clase java.lang.String
int
booleano
clase java.lang.String
int
int

Ahora agreguemos un método a nuestra clase para obtener el nombre de un campo. Esto facilitará la navegación por los campos de nuestra clase.


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

Ahora el resultado es más comprensible:

Tipo de campo - clase java.lang.String
Nombre de campo - nombre

Tipo de campo - int
Nombre de campo - edad

Tipo de campo - booleano
Nombre de campo - isMale

Tipo de campo - clase java.lang.String
Nombre de campo - dirección

Tipo de campo - int
Nombre de campo - MAX_AGE

Tipo de campo - int
Nombre de campo - MIN_AGE

¡Excelente! ¡Vamos a modificar nuestro método un poco más! Agregaremos modificadores de acceso aquí


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

Y veamos qué devuelve e.getModifiers() . Este método devuelve un int que nos permite determinar el estado de los modificadores de acceso de nuestro campo. La clase Modifier contiene variables estáticas responsables de cada modificador específico del campo.

Envolvamos nuestro valor de retorno en Modifier.toString() e inmediatamente obtengamos su valor como texto:

Tipo de campo: clase java.lang.String
Nombre de campo: nombre
Modificadores: privado

Tipo
de campo: int Nombre de campo: edad
Modificadores: privado

Tipo de campo: booleano
Nombre de campo: isMale
Modificadores: público

Tipo de campo: clase java.lang.String
Nombre de campo: Modificadores de dirección
: protegido

Tipo de campo: int
Nombre de campo: MAX_AGE
Modificadores: final estático público

Tipo de campo: int
Nombre de campo: MIN_AGE
Modificadores: final estático público

Así es como se ve sin Modifier.toString() :

Tipo de campo: clase java.lang.String
Nombre de campo: nombre
Modificadores: 2

Tipo de campo: int
Nombre de campo: edad
Modificadores: 2

Tipo de campo: booleano
Nombre de campo: isMale
Modificadores: 1

Tipo de campo: clase java.lang.String
Nombre de campo: dirección
Modificadores - 4

Tipo de campo - int
Nombre de campo - MAX_AGE
Modificadores - 25

Tipo de campo - int
Nombre de campo - MIN_AGE
Modificadores - 25

métodos getAnnotations(), getAnnotatedType() y getGenericType()

Modifiquemos la clase Person para que funcione con estos métodos. Escribiremos nuestra propia anotación y la aplicaremos a nuestros campos. Y agregaremos algunos campos más.

Vamos a crear dos anotaciones. Pasaremos el nombre de la variable en Pig Latin a uno, y usaremos el segundo para los elementos:


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

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

Y cambiaremos nuestra clase principal y la clase 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())));
}

Es hora de ver los resultados de nuestros métodos y descubrir para qué sirven:

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

getGenericType:
java.lang.Class<java.lang. Objeto>
java.util.List<java.lang.String>
clase java.lang.String
clase [I

getAnnotations:
[]
[@Name(name="\u0055\u0073\u0065\u0072\u0020\u006e\u0069\u0063 \u006b\u006e\u0061\u006d\u0065\u0073")]
[@Nombre(nombre="\u0041\u006d\u0065\u002d\u006e\u0061\u0079")] [
]
  • getAnnotatedType devuelve la anotación del campo dado, si corresponde. Obtenemos la anotación para el campo y podemos verlo perfectamente.

  • getGenericType le permite mostrar correctamente un tipo parametrizado.

  • getAnnotations devuelve las anotaciones aplicadas a nuestro objeto.

Así es como podemos obtener fácilmente todos los datos de cada campo de nuestra clase, así como sus modificadores de acceso, anotaciones y tipos de datos.

clase java.lang.reflect.Method

¡Súper! Hemos hablado de los campos de nuestra clase. Ahora es el momento de hablar de los métodos.

Para obtener un objeto Método , llamamos al método getMethod , pasándole el nombre de nuestro método. Esta es la forma básica de obtener un objeto Método :


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

Continuaremos trabajando con nuestra clase. Agreguemos getters y setters, y los métodos hashCode, equals y 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);
    }
}

Ahora preparemos un conjunto de métodos que examinaremos usando la clase Method . Aquí hay una lista de los métodos más importantes:

Método Descripción
obtenerNombre() Devuelve el nombre del método.
obtenerModificadores() Devuelve el modificador de acceso de este método.
getReturnType() Devuelve el tipo de retorno del método.
getGenericReturnType() Devuelve el tipo de valor devuelto del método, teniendo en cuenta los métodos genéricos.
getParameterTypes() Devuelve una matriz de parámetros de método.
getGenericParameterTypes() Devuelve una matriz de parámetros de método, teniendo en cuenta los métodos genéricos.
getExceptionTypes() Devuelve las excepciones que puede lanzar el método.
getGenericExceptionTypes() Devuelve las excepciones que puede generar el método, teniendo en cuenta los tipos parametrizados.
obtener anotaciones() Devuelve las anotaciones del método, incluidas las anotaciones principales.
getDeclaredAnnotations() Devuelve las anotaciones del método, excluyendo las anotaciones principales.

Para obtener una matriz de los métodos de nuestra clase, podemos llamar a este método:


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

Nos dará todos los métodos de nuestra clase.

métodos getName() y getModifiers()

Podemos usar getName para obtener el nombre de cada método:


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

Ahora, para obtener los modificadores, escribamos un método que use getModifiers :


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

Aquí está nuestro método principal :


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

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

Nuestro resultado:

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

Todos nuestros métodos tienen el modificador público , por lo que el último método devuelve una matriz de unos. Si modificamos nuestro código, veremos nuestros propios modificadores:


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

Este método nos permite obtener el tipo de retorno del método:


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

getGenericReturnType()

Démosle a nuestra clase Person un método que devuelva el tipo envuelto en un tipo parametrizado e intentemos obtener su valor de retorno:


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

Y actualizaremos nuestro método principal:


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

Resultado de nuestro método:

clase java.lang.String
booleano
clase java.lang.String
int
void
clase java.lang.String
booleano
int
void
void
void
java.util.List<java.lang.String>

métodos getParameterTypes() y getGenericParameterTypes()

Seguimos modificando nuestra clase Person , agregando dos métodos más:


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

El primero nos permitirá obtener los parámetros de nuestros métodos, y el segundo también nos dará tipos parametrizados.


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

Accederemos solo a uno de nuestros métodos. Para acceder a un método por un nombre específico, llamamos a getMethod y pasamos el nombre y los parámetros del método que queremos:


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

Ejecutando nuestro código, veremos cómo difieren los métodos y qué devuelven:

interfaz java.util.List
clase java.lang.String

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

métodos getExceptionTypes() y getGenericExceptionTypes()

Podemos usar estos métodos para obtener una serie de excepciones que nuestro método puede lanzar, así como excepciones con tipos parametrizados (si los hay). Usemos un nuevo ejemplo que tiene una clase estática oculta:


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

    private void process() throws IOException {}
}

Y llamaremos métodos en nuestra clase 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));
}

Ahora podemos ver nuestra excepción:

[clase java.io.IOException]

Ahora vamos a parametrizar el tipo. Modificaremos nuestra clase principal:


private static class Processor<E extends IOException> {

    private void process() throws E {
    }
}

Y el código del método principal :


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

Dentro de este método, tenemos un objeto TypeVariables , que es una interfaz principal genérica para variables de tipo. Y dentro de eso, ahora podemos obtener el parámetro interno, es decir, nuestra excepción anidada:

[E]
clase java.io.IOException

métodos getAnnotations() y getDeclaredAnnotations()

Sigamos usando esta nueva clase y agreguemos un par de anotaciones. Crearemos nuestra propia anotación Anotación:


@Retention(RetentionPolicy.RUNTIME)
@interface Annotation {

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

Y aplicarlo a nuestro método:


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

}

Y, por supuesto, agregaremos un método para mostrar todas nuestras anotaciones:


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

Implementación de nuestro método principal :


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

La salida de pantalla resultante es:

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

Así es como podemos obtener las anotaciones que se han aplicado a nuestros métodos, y el método getAnnotations también nos permite acceder a las anotaciones principales de la clase.

¡Hoy nos familiarizamos con cómo la reflexión puede funcionar con métodos y campos, y qué datos podemos obtener con ella!