CodeGym /Cursos Java /Módulo 2: Núcleo Java /Criando objetos com reflexo

Criando objetos com reflexo

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

Exemplo de criação de um objeto usando Class.newInstance()

Imagine que você foi designado para criar um objeto usando reflexão. Vamos começar?

Começaremos escrevendo o código da classe que queremos instanciar:


public class Employee {
    private String name;
    private String lastName;
    private int age;

    {
        age = -1;
        name = "Rob";
        surname = "Stark";
    }

    public Employee(String name, String surname, int age) {
        this.name = name;
        this.surname = surname;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public String getSurname() {
        return lastName;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

    public int getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", surname='" + surname + '\'' +
                ", age=" + age +
                '}';
    }
}

Esta será nossa classe — com vários campos, um construtor com parâmetros, getters e setters, um método toString() e um bloco de inicialização. Agora vamos para a segunda parte: criar um objeto usando reflexão. A primeira abordagem que veremos usará Class.newInstance() .


public class Main {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        Employee employee = Employee.class.newInstance();
        System.out.println("age is " + employee.getAge());
    }
}

Excelente! Vamos executar nosso código e observar a idade a ser exibida. Mas recebemos um erro sobre um construtor padrão ausente. Acontece que esse método nos permite apenas obter um objeto criado usando um construtor padrão. Vamos adicionar um construtor padrão para nossa classe e testar o código novamente.

Mensagem de erro:

Código do novo construtor


public Employee() { }

Depois de adicionar o construtor, aqui está a saída:

a idade é 1

Ótimo! Descobrimos como esse método funciona. Agora vamos dar uma olhada sob o capô. Abrindo a documentação, vemos que nosso método já está obsoleto :

Ele também pode gerar InstantiationException e IllegalAccessException . Assim, a documentação sugere que usemos outra forma de criar um objeto, ou seja, Constructor.newInstance() . Vamos analisar detalhadamente como funciona a classe Construtor .

Métodos getConstructors e getDeclaredConstructors

Para trabalhar com a classe Constructor , primeiro precisamos obter uma instância. Temos dois métodos para isso: getConstructors e getDeclaredConstructors .

O primeiro retorna uma matriz de construtores públicos e o segundo retorna uma matriz de todos os construtores de classe.

Vamos dar um pouco de privacidade à nossa classe, ou melhor, vamos criar alguns construtores privados para ajudar a demonstrar como esses métodos funcionam.

Vamos adicionar alguns construtores privados:


private Employee(String name, String surname) {
    this.name = name;
    this.lastName = lastName;
}

Observando o código, observe que um dos construtores é privado:

Vamos testar nossos métodos:


public class Main {
	  public static void main(String[] args) {
	      Class employeeClass = Employee.class;
	
	      System.out.println("getConstructors:");
	      printAllConstructors(employeeClass);
	
	      System.out.println("\n" +"getDeclaredConstructors:");
	      printDeclaredConstructors(employeeClass);
	  }
	
	  static void printDeclaredConstructors(Class<?> c){
	      for (Constructor<?> constructor : c.getDeclaredConstructors()) {
	          System.out.println(constructor);
	      }
	  }
	
	  static void printAllConstructors(Class<?> c){
	      for (Constructor<?> constructor : c.getConstructors()) {
	          System.out.println(constructor);
	      }
	  }
}

E obtemos este resultado:

getConstructors:
public com.codegym.Employee(java.lang.String,java.lang.String,int)
public.com.codegym.Employee()

getDeclaredConstructors:
private com.codegym.Employee(java.lang.String,java.lang .String)
public com.codegym.Employee(java.lang.String,java.lang.String,int)
public com.codegym.Employee()

Ok, é assim que obtemos acesso ao objeto Constructor . Agora podemos falar sobre o que ele pode fazer.

A classe java.lang.reflect.Constructor e seus métodos mais importantes

Vamos dar uma olhada nos métodos mais importantes e como eles funcionam:

Método Descrição
getNome() Retorna o nome desse construtor como uma string.
getModifiers() Retorna os modificadores de acesso Java codificados como um número.
getExceptionTypes() Retorna um array de objetos Class que representam os tipos de exceções declaradas pelo construtor.
getParameters() Retorna uma matriz de objetos Parameter representando todos os parâmetros. Retorna uma matriz de comprimento 0 se o construtor não tiver parâmetros.
getParameterTypes() Retorna uma matriz de objetos Class que representam os tipos de parâmetros formais na ordem de declaração.
getGenericParameterTypes() Retorna uma matriz de objetos Type que representam os tipos de parâmetros formais na ordem de declaração.

getNome() & getModifiers()

Vamos envolver nossa matriz em uma lista para torná-la conveniente para trabalhar. Também escreveremos os métodos getName e getModifiers :


static List<Constructor<?>> getAllConstructors(Class<?> c) {
    return new ArrayList<>(Arrays.asList(c.getDeclaredConstructors()));
}

static List<String> getConstructorNames(List<Constructor<?>> constructors) {
    List<String> result = new ArrayList<>();
    for (Constructor<?> constructor : constructors) {
        result.add(constructor.toString());
    }
    return result;
}

static List<String> getConstructorModifiers(List<Constructor<?>> constructors) {
    List<String> result = new ArrayList<>();
    for (Constructor<?> constructor : constructors) {
        result.add(Modifier.toString(constructor.getModifiers()));
    }
    return result;
}

E nosso método principal , onde vamos chamar tudo:


public static void main(String[] args) {
    Class employeeClass = Employee.class;
    var constructors = getAllConstructors(employeeClass);
    var constructorNames = getConstructorNames(constructors);
    var constructorModifiers = getConstructorModifiers(constructors);

    System.out.println("Employee class:");
    System.out.println("Constructors :");
    System.out.println(constructorNames);
    System.out.println("Modifiers :");
    System.out.println(constructorModifiers);
}

E agora vemos todas as informações que queremos:

Classe Employee:
Construtores:
[private com.codegym.Employee(java.lang.String), public
com.codegym.Employee(java.lang.String,java.lang.String,int), public com.codegym.Employee() ]
Modificadores:
[privado, público, público]

getExceptionTypes()

Este método nos permite obter um array das exceções que nosso construtor pode lançar. Vamos modificar um de nossos construtores e escrever um novo método.

Aqui mudamos ligeiramente nosso construtor atual:


private Employee(String name, String surname) throws Exception {
    this.name = name;
    this.lastName = lastName;
}

E aqui temos um método para obter tipos de exceção e adicioná-los ao main :


static List<Class<?>> getConstructorExceptionTypes(Constructor<?> c) {
      return new ArrayList<>(Arrays.asList(c.getExceptionTypes()));
}


var constructorExceptionTypes = getConstructorExceptionTypes(constructors.get(0));
System.out.println("Exception types :");
System.out.println(constructorExceptionTypes);

Acima, acessamos o primeiro construtor da nossa lista. Discutiremos como obter um construtor específico um pouco mais tarde.

E veja a saída depois de adicionar throws Exception :

Tipos de exceção:
[class java.lang.Exception]

E antes de adicionar a exceção:

Tipos de exceção:
[]

Tudo é maravilhoso, mas como vemos quais parâmetros são exigidos por nossos construtores? Vamos descobrir isso também.

getParameters() & getParameterTypes() & getGenericParameterTypes()

Vamos começar novamente refinando nosso construtor privado. Agora vai ficar assim:


private Employee(String name, String surname, List<String> list) {
    this.name = name;
    this.lastName = lastName;
}

E temos três métodos adicionais: getParameters para obter a ordem dos parâmetros e seus tipos, getParameterTypes para obter os tipos de parâmetro e getGenericParameterTypes para obter tipos agrupados em genéricos .


static List<Parameter> getConstructorParameters(Constructor<?> c) {
    return new ArrayList<>(Arrays.asList(c.getParameters()));
}

static List<Class<?>> getConstructorParameterTypes(Constructor<?> c) {
    return new ArrayList<>(Arrays.asList(c.getParameterTypes()));
}

static List<Type> getConstructorParametersGenerics(Constructor<?> c) {
    return new ArrayList<>(Arrays.asList(c.getGenericParameterTypes()));
}

E adicionamos mais algumas informações ao nosso já não tão pequeno método principal :


var constructorParameterTypes = getConstructorParameterTypes(constructors.get(0));
var constructorParameters = getConstructorParameters(constructors.get(0));
var constructorParametersGenerics = getConstructorParametersGenerics(constructors.get(0));

System.out.println("Constructor parameters :");
System.out.println(constructorParameters);

System.out.println("Parameter types :");
System.out.println(constructorParameterTypes);

System.out.println("Constructor parameter types :");
System.out.println(constructorParametersGenerics);

Olhando para a saída, vemos informações muito detalhadas sobre os parâmetros de nossos construtores:

Parâmetros do construtor:
[java.lang.String arg0, java.lang.String arg1, java.util.List<java.lang.String> arg2]
Tipos de parâmetro:
[class java.lang.String, classe java.lang.String, interface java.util.List]
Tipos de parâmetros do construtor:
[class java.lang.String, class java.lang.String, java.util.List<java.lang.String>]

Isso demonstra claramente a diferença entre cada método. Vemos que temos opções separadas para obter informações sobre tipos de parâmetros, tipos agrupados e tudo em geral. Super! Agora que estamos familiarizados com a classe Constructor , podemos voltar ao tópico principal do nosso artigo — criar objetos.

Criando um objeto usando Constructor.newInstance()

A segunda maneira de criar objetos é chamar o método newInstance no construtor. Vamos dar uma olhada em um exemplo de trabalho e ver como podemos obter um determinado construtor.

Se você deseja obter um único construtor, deve usar o método getConstructor (não confundir com getConstructors , que retorna uma matriz de todos os construtores). O método getConstructor retorna o construtor padrão.


public static void main(String[] args) throws NoSuchMethodException {
    Class employeeClass = Employee.class;
    Constructor<?> employeeConstructor = employeeClass.getConstructor();
    System.out.println(employeeConstructor);
}
public com.codegym.Employee()

E se quisermos obter um construtor específico, precisamos passar os tipos de parâmetro do construtor para este método.

Não esqueça que só podemos obter nosso construtor privado usando o método getDeclaredConstructor .


Constructor<?> employeeConstructor2 = employeeClass.getDeclaredConstructor(String.class, String.class, List.class);
System.out.println(employeeConstructor2);

É assim que podemos obter um construtor específico. Agora vamos tentar criar objetos usando construtores privados e públicos.

Construtor público:


Class employeeClass = Employee.class;
Constructor<?> employeeConstructor = employeeClass.getConstructor(String.class, String.class, int.class);
System.out.println(employeeConstructor);

Employee newInstance = (Employee) employeeConstructor.newInstance("Rob", "Stark", 10);
System.out.println(newInstance);

O resultado é um objeto com o qual podemos trabalhar:

public com.codegym.Employee(java.lang.String,java.lang.String,int)
Funcionário{name='Rob' surname='Stark', idade=10}

Tudo funciona muito bem! Agora vamos tentar com um construtor privado:


Constructor<?> declaredConstructor = employeeClass.getDeclaredConstructor(String.class, String.class, List.class);
System.out.println(declaredConstructor);

Employee newInstance2 = (Employee) declaredConstructor.newInstance("Rob", "Stark", new ArrayList<>());
System.out.printf(newInstance2.toString());

O resultado é um erro sobre a privacidade do nosso construtor:

Java não pode criar um objeto usando este construtor, mas na verdade há algo mágico que podemos fazer no método main . Podemos acessar o nível de acesso do nosso construtor, possibilitando a criação de objetos da nossa classe:


declaredConstructor.setAccessible(true);

Resultado da criação de um objeto

private com.codegym.Employee(java.lang.String,java.lang.String,java.util.List)
Funcionário{name='Rob', sobrenome='Stark', idade=-1}

Não definimos a idade em nosso construtor, então ela permanece a mesma de quando foi inicializada.

Maravilhoso, vamos resumir!

Benefícios de criar objetos usando Constructor.newInstance()

Ambos os métodos compartilham o mesmo nome, mas existem diferenças entre eles:

Class.newInstance() Construtor.newInstance()
Só pode chamar um construtor sem argumento . Pode chamar qualquer construtor independentemente do número de parâmetros.
Requer que o construtor esteja visível. Também pode chamar construtores privados sob certas circunstâncias.
Lança qualquer exceção (marcada ou não) declarada pelo construtor. Sempre envolve uma exceção gerada com InvocationTargetException .

Por esses motivos, Constructor.newInstance() é preferível a Class.newInstance() , e é o método usado por vários frameworks e APIs, como Spring, Guava, Zookeeper, Jackson, Servlet, etc.

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