CodeGym /Cursos Java /Módulo 2: Núcleo Java /Obtendo dados com reflexão

Obtendo dados com reflexão

Módulo 2: Núcleo Java
Nível 17 , Lição 1
Disponível

classe java.lang.reflect.Field

A classe Field fornece informações e acesso dinâmico a um único campo de uma classe ou interface. Field também permite uma conversão de tipo de ampliação durante uma operação de acesso get ou set, mas lança um IllegalArgumentException se ocorrer estreitamento.

Para obter um objeto Field , primeiro escreveremos uma 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;
}

E aqui está o nosso código para trabalhar com essa 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);
    }
}

É assim que obtemos a lista dos campos da nossa classe, com os quais trabalharemos posteriormente. Aqui está o resultado:

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

Agora vamos descobrir o que podemos fazer com esses dados. Vamos falar sobre os métodos da classe Field :

Método Descrição
getType() Retorna um objeto Class que identifica o tipo declarado do campo representado por esteCampoobjeto.
getAnnotatedType() Retorna umTipo anotadoobjeto que representa o uso de um tipo para especificar o tipo declarado do campo representado por este Field.
getGenericType() Retorna umTipoobjeto que representa o tipo declarado do campo representado por esteCampoobjeto.
getNome() Retorna o nome do campo representado por esteCampoobjeto.
getModifiers() Retorna um int codificando os modificadores de linguagem Java para o campo representado por esteCampoobjeto.
getAnnotations() Retorna as anotações para este campo. Se não houver anotações, ele retorna uma matriz vazia.

Métodos getType(), getName() e getModifiers()

Podemos usar o método getType() para obter o tipo do nosso campo. Vamos escrever um método:


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

E obtemos este resultado:

classe java.lang.String
int classe
booleana
java.lang.String
int
int

Agora vamos adicionar um método à nossa classe para obter o nome de um campo. Isso facilitará a navegação pelos campos da nossa 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()));
}

Agora o resultado é mais compreensível:

Tipo de campo - classe java.lang.String
Nome do campo - nome

Tipo de campo - int
Nome do campo - idade

Tipo de campo - booleano
Nome do campo - isMale

Tipo de campo - classe java.lang.String
Nome do campo - endereço

Tipo de campo - int
Nome do campo - MAX_AGE

Tipo de campo - int
Nome do campo - MIN_AGE

Ótimo! Vamos modificar um pouco mais nosso método! Adicionaremos modificadores de acesso aqui


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 vamos ver o que e.getModifiers() retorna. Este método retorna um int que nos permite determinar o estado dos modificadores de acesso do nosso campo. A classe Modifier contém variáveis ​​estáticas responsáveis ​​por cada modificador específico do campo.

Vamos agrupar nosso valor de retorno em Modifier.toString() e obter imediatamente seu valor como texto:

Tipo de campo - classe java.lang.String
Nome do campo -
modificadores de nome - privado

Tipo de
campo - int Nome do campo - idade
Modificadores - privado

Tipo de campo - booleano
Nome do campo - isMale
Modificadores - público

Tipo de campo - classe java.lang.String
Nome do campo - endereço
Modificadores - protegido

Tipo de campo - int
Nome do campo - MAX_AGE
Modificadores - public static final

Tipo de campo - int
Nome do campo - MIN_AGE
Modificadores - public static final

Aqui está o que parece sem Modifier.toString() :

Tipo de campo - classe java.lang.String
Nome do campo -
modificadores de nome - 2

Tipo de campo - int
Nome do campo - idade
Modificadores - 2

Tipo de campo - booleano
Nome do campo - isMale
Modificadores - 1

Tipo de campo - classe java.lang.String
Nome do campo - Modificadores de endereço
- 4

Tipo de campo - int
Nome do campo - MAX_AGE
Modificadores - 25

Tipo de campo - int
Nome do campo - MIN_AGE
Modificadores - 25

Métodos getAnnotations(), getAnnotatedType() e getGenericType()

Vamos modificar a classe Person para trabalhar com esses métodos. Vamos escrever nossa própria anotação e aplicá-la aos nossos campos. E adicionaremos mais alguns campos.

Vamos criar duas anotações. Passaremos o nome da variável em Pig Latin para um, e usaremos o segundo para os elementos:


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

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

E vamos mudar nossa classe principal e a 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())));
}

É hora de olhar para os resultados de nossos métodos e descobrir para que eles servem:

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

getGenericType:
java.lang.Class<java.lang. Object>
java.util.List<java.lang.String>
classe java.lang.String
classe [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 retorna a anotação para o campo fornecido, se houver. Obtemos a anotação do campo e podemos vê-la perfeitamente.

  • getGenericType permite exibir corretamente um tipo parametrizado.

  • getAnnotations retorna as anotações aplicadas ao nosso objeto.

É assim que podemos obter facilmente todos os dados sobre cada campo de nossa classe, bem como seus modificadores de acesso, anotações e tipos de dados.

classe java.lang.reflect.Method

Super! Nós conversamos sobre os campos de nossa classe. Agora é hora de falar sobre os métodos.

Para obter um objeto Method , chamamos o método getMethod , passando a ele o nome do nosso método. Esta é a maneira básica de obter um objeto Method :


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

Vamos continuar a trabalhar com a nossa turma. Vamos adicionar getters e setters e métodos 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);
    }
}

Agora vamos preparar um conjunto de métodos que examinaremos usando a classe Method . Aqui está uma lista dos métodos mais importantes:

Método Descrição
getNome() Retorna o nome do método.
getModifiers() Retorna o modificador de acesso deste método.
getReturnType() Retorna o tipo de retorno do método.
getGenericReturnType() Retorna o tipo de retorno do método, contabilizando métodos genéricos.
getParameterTypes() Retorna uma matriz de parâmetros de método.
getGenericParameterTypes() Retorna uma matriz de parâmetros de método, contabilizando métodos genéricos.
getExceptionTypes() Retorna as exceções que o método pode lançar.
getGenericExceptionTypes() Retorna as exceções que o método pode lançar, considerando os tipos parametrizados.
getAnnotations() Retorna as anotações para o método, incluindo anotações pai.
getDeclaredAnnotations() Retorna as anotações do método, excluindo as anotações pai.

Para obter um array dos métodos da nossa classe will, podemos chamar este método:


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

Ele nos dará todos os métodos em nossa classe.

Métodos getName() e getModifiers()

Podemos usar getName para obter o nome 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);
}

Agora, para obter os modificadores, vamos escrever um método que usa getModifiers :


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

Aqui está o nosso método principal :


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

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

Nosso resultado:

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

Todos os nossos métodos têm o modificador público , então o último método retorna um array de uns. Se modificarmos nosso código, veremos nossos próprios 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, public]

getReturnedType()

Este método nos permite obter o tipo de retorno do método:


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

getGenericReturnType()

Vamos dar à nossa classe Person um método que retorne o tipo envolvido em um tipo parametrizado e tentar obter seu valor de retorno:


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

E atualizaremos nosso método principal:


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

Resultado do nosso método:

classe java.lang.String classe
booleana java.lang.String int void classe java.lang.String boolean int void void void java.util.List<java.lang.String>









Métodos getParameterTypes() e getGenericParameterTypes()

Continuamos a modificar nossa classe Person , adicionando mais dois métodos:


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

O primeiro nos permitirá obter os parâmetros de nossos métodos, e o segundo também 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);
        }
}

Vamos acessar apenas um dos nossos métodos. Para acessar um método por um nome específico, chamamos getMethod e passamos o nome e os parâmetros do 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);
}

Executando nosso código, veremos como os métodos diferem e o que eles retornam:

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

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

Métodos getExceptionTypes() e getGenericExceptionTypes()

Podemos usar esses métodos para obter uma matriz de exceções que nosso método pode lançar, bem como exceções com tipos parametrizados (se houver). Vamos usar um novo exemplo que possui uma classe estática oculta:


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

    private void process() throws IOException {}
}

E vamos chamar métodos em nossa 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));
}

Agora podemos ver nossa exceção:

[classe java.io.IOException]

Agora vamos parametrizar o tipo. Modificaremos nossa classe principal:


private static class Processor<E extends IOException> {

    private void process() throws E {
    }
}

E o código do método 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);
            }
        }
    }
}

Dentro desse método, temos um objeto TypeVariables , que é uma interface pai genérica para variáveis ​​de tipo. E dentro disso, agora podemos obter o parâmetro interno, ou seja, nossa exceção aninhada:

[E]
classe java.io.IOException

Métodos getAnnotations() e getDeclaredAnnotations()

Vamos continuar usando essa nova classe e adicionar algumas anotações a ela. Vamos criar nossa própria anotação de anotação :


@Retention(RetentionPolicy.RUNTIME)
@interface Annotation {

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

E aplicá-lo ao nosso método:


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

}

E, claro, adicionaremos um método para exibir todas as nossas anotações:


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

Implementação do nosso método principal :


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

A saída de tela resultante é:

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

É assim que podemos obter as anotações que foram aplicadas aos nossos métodos, e o método getAnnotations também nos permite acessar as anotações pai da classe.

Hoje conhecemos como a reflexão pode trabalhar com métodos e campos e quais dados podemos obter com ela!

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION