Ejemplo de creación de un objeto usando Class.newInstance()

Imagina que te asignan la tarea de crear un objeto usando la reflexión. ¿Empezamos?

Comenzaremos escribiendo el código de la clase 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á nuestra clase, con varios campos, un constructor con parámetros, getters y setters, un método toString() y un bloque de inicialización. Ahora pasemos a la segunda parte: crear un objeto usando la reflexión. El primer enfoque 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! Ejecutemos nuestro código y observemos si se muestra la edad. Pero recibimos un error sobre un constructor predeterminado faltante. Resulta que este método solo nos permite obtener un objeto creado usando un constructor predeterminado. Agreguemos un constructor predeterminado para nuestra clase y probemos el código nuevamente.

Mensaje de error:

Código del nuevo constructor

public Employee() { }

Después de agregar el constructor, aquí está el resultado:

la edad es 1

¡Excelente! Descubrimos cómo funciona este método. Ahora echemos un vistazo debajo del capó. Al abrir la documentación, vemos que nuestro método ya está en desuso :

También puede generar InstanciationException e IllegalAccessException . En consecuencia, la documentación sugiere que usemos la otra forma de crear un objeto, a saber, Constructor.newInstance() . Analicemos en detalle cómo funciona la clase Constructor .

métodos getConstructors y getDeclaredConstructors

Para trabajar con la clase Constructor , primero necesitamos obtener una instancia. Tenemos dos métodos para esto: getConstructors y getDeclaredConstructors .

El primero devuelve una matriz de constructores públicos y el segundo devuelve una matriz de todos los constructores de clase.

Démosle a nuestra clase algo de privacidad, o más bien, creemos algunos constructores privados para ayudar a demostrar cómo funcionan estos métodos.

Agreguemos algunos constructores privados:

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

Mirando el código, tenga en cuenta que uno de los constructores es privado:

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

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

Bien, así es como obtenemos acceso al objeto Constructor . Ahora podemos hablar de lo que puede hacer.

La clase java.lang.reflect.Constructor y sus métodos más importantes

Echemos un vistazo a los métodos más importantes y cómo funcionan:

Método Descripción
obtenerNombre() Devuelve el nombre de este constructor como una cadena.
obtenerModificadores() Devuelve los modificadores de acceso de Java codificados como un número.
getExceptionTypes() Devuelve una matriz de objetos Class que representan los tipos de excepciones declarados por el constructor.
getParameters() Devuelve una matriz de objetos de parámetro que representan todos los parámetros. Devuelve una matriz de longitud 0 si el constructor no tiene parámetros.
getParameterTypes() Devuelve una matriz de objetos Class que representan los tipos de parámetros formales en orden de declaración.
getGenericParameterTypes() Devuelve una matriz de objetos Type que representan los tipos de parámetros formales en orden de declaración.

getNombre() & getModificadores()

Envolvamos nuestra matriz en una Lista para que sea conveniente trabajar con ella. También escribiremos los métodos getName y 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;
}

Y nuestro método principal , donde llamaremos a todo:

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

Y ahora vemos toda la información que queremos:

Clase de empleado:
Constructores:
[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 obtener una matriz de las excepciones que nuestro constructor puede lanzar. Modifiquemos uno de nuestros constructores y escribamos un nuevo método.

Aquí cambiamos ligeramente nuestro constructor actual:

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

Y aquí tenemos un método para obtener tipos de excepción y agregarlo a 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);

Arriba, accedimos al primer constructor de nuestra lista. Discutiremos cómo obtener un constructor específico un poco más tarde.

Y mire la salida después de agregar throws Exception :

Tipos de excepción:
[clase java.lang.Exception]

Y antes de agregar la excepción:

Tipos de excepción:
[]

Todo es maravilloso, pero ¿cómo vemos qué parámetros requieren nuestros constructores? Vamos a averiguar esto también.

getParameters() & getParameterTypes() & getGenericParameterTypes()

Empecemos de nuevo refinando nuestro constructor privado. Ahora se verá así:

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

Y tenemos tres métodos adicionales: getParameters para obtener el orden de los parámetros y sus tipos, getParameterTypes para obtener los tipos de parámetros y getGenericParameterTypes para obtener tipos envueltos en 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()));
}

Y agregamos algo más de información a nuestro ya no tan pequeño 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);

Mirando la salida, vemos información muy detallada sobre los parámetros de nuestros constructores:

Parámetros del constructor:
[java.lang.String arg0, java.lang.String arg1, java.util.List<java.lang.String> arg2]
Tipos de parámetros:
[clase java.lang.String, clase java.lang.String, interfaz java.util.List]
Tipos de parámetros de constructor:
[clase java.lang.String, clase java.lang.String, java.util.List<java.lang.String>]

Esto demuestra claramente la diferencia entre cada método. Vemos que tenemos opciones separadas para obtener información sobre tipos de parámetros, tipos envueltos y todo en general. ¡Súper! Ahora que estamos familiarizados con la clase Constructor , podemos volver al tema principal de nuestro artículo: la creación de objetos.

Creando un objeto usando Constructor.newInstance()

La segunda forma de crear objetos es llamar al método newInstance en el constructor. Echemos un vistazo a un ejemplo de trabajo y veamos cómo podemos obtener un constructor en particular.

Si desea obtener un solo constructor, debe usar el método getConstructor (que no debe confundirse con getConstructors , que devuelve una matriz de todos los constructores). El método getConstructor devuelve el constructor predeterminado.

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

Y si queremos obtener un constructor específico, debemos pasar los tipos de parámetros del constructor a este método.

No olvide que solo podemos obtener nuestro constructor privado usando el método getDeclaredConstructor .

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

Así es como podemos obtener un constructor específico. Ahora intentemos crear objetos usando constructores públicos y privados.

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

El resultado es un objeto con el que podemos trabajar:

public com.codegym.Employee(java.lang.String,java.lang.String,int)
Empleado{nombre='Rob' apellido='Stark', edad=10}

¡Todo funciona muy bien! Ahora probaremos con un constructor 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());

El resultado es un error sobre la privacidad de nuestro constructor:

Java no pudo crear un objeto usando este constructor, pero en realidad hay algo mágico que podemos hacer en el método principal . Podemos acceder al nivel de acceso de nuestro constructor, haciendo posible la creación de objetos de nuestra clase:

declaredConstructor.setAccessible(true);

Resultado de crear un objeto

privado com.codegym.Employee(java.lang.String,java.lang.String,java.util.List)
Empleado{nombre='Rob', apellido='Stark', edad=-1}

No establecemos la edad en nuestro constructor, por lo que permanece igual que cuando se inicializó.

¡Maravilloso, resumamos!

Beneficios de crear objetos usando Constructor.newInstance()

Ambos métodos comparten el mismo nombre, pero existen diferencias entre ellos:

Class.newInstance() Constructor.newInstance()
Solo puede llamar a un constructor sin argumentos . Puede llamar a cualquier constructor independientemente del número de parámetros.
Requiere que el constructor sea visible. También puede llamar a constructores privados bajo ciertas circunstancias.
Lanza cualquier excepción (marcada o no) declarada por el constructor. Siempre envuelve una excepción lanzada con InvocationTargetException .

Por estas razones, se prefiere Constructor.newInstance() sobre Class.newInstance() , y es el método utilizado por varios marcos y API como Spring, Guava, Zookeeper, Jackson, Servlet, etc.