Esempio di creazione di un oggetto utilizzando Class.newInstance()

Immagina di essere incaricato di creare un oggetto usando la riflessione. Cominciamo?

Inizieremo scrivendo il codice per la classe che vogliamo istanziare:


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

Questa sarà la nostra classe — con diversi campi, un costruttore con parametri, getter e setter, un metodo toString() e un blocco di inizializzazione. Ora passiamo alla seconda parte: creare un oggetto usando la riflessione. Il primo approccio che esamineremo utilizzerà 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());
    }
}

Eccellente! Eseguiamo il nostro codice e osserviamo che l'età viene visualizzata. Ma otteniamo un errore relativo a un costruttore predefinito mancante. Si scopre che questo metodo ci consente solo di ottenere un oggetto creato utilizzando un costruttore predefinito. Aggiungiamo un costruttore predefinito per la nostra classe e testiamo nuovamente il codice.

Messaggio di errore:

Codice del nuovo costruttore


public Employee() { }

Dopo aver aggiunto il costruttore, ecco l'output:

l'età è 1

Grande! Abbiamo capito come funziona questo metodo. Ora diamo un'occhiata sotto il cofano. Aprendo la documentazione, vediamo che il nostro metodo è già deprecato :

Può anche lanciare InstantiationException e IllegalAccessException . Di conseguenza, la documentazione suggerisce di utilizzare l'altro modo di creare un oggetto, vale a dire Constructor.newInstance() . Analizziamo nel dettaglio come funziona la classe Constructor .

metodi getConstructors e getDeclaredConstructors

Per lavorare con la classe Constructor , dobbiamo prima ottenere un'istanza. Abbiamo due metodi per questo: getConstructors e getDeclaredConstructors .

Il primo restituisce un array di costruttori pubblici e il secondo restituisce un array di tutti i costruttori di classi.

Concediamo alla nostra classe un po' di privacy, o meglio, creiamo alcuni costruttori privati ​​per aiutare a dimostrare come funzionano questi metodi.

Aggiungiamo alcuni costruttori privati:


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

Guardando il codice, nota che uno dei costruttori è privato:

Testiamo i nostri metodi:


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 otteniamo questo risultato:

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, è così che otteniamo l'accesso all'oggetto Constructor . Ora possiamo parlare di cosa può fare.

La classe java.lang.reflect.Constructor ei suoi metodi più importanti

Diamo un'occhiata ai metodi più importanti e al loro funzionamento:

Metodo Descrizione
getNome() Restituisce il nome di questo costruttore come stringa.
getModifiers() Restituisce i modificatori di accesso Java codificati come numero.
getExceptionTypes() Restituisce una matrice di oggetti Class che rappresentano i tipi di eccezioni dichiarati dal costruttore.
getParametri() Restituisce una matrice di oggetti Parameter che rappresentano tutti i parametri. Restituisce una matrice di lunghezza 0 se il costruttore non ha parametri.
getParameterTypes() Restituisce una matrice di oggetti Class che rappresentano i tipi di parametro formali nell'ordine di dichiarazione.
getGenericParameterTypes() Restituisce una matrice di oggetti Type che rappresentano i tipi di parametri formali nell'ordine di dichiarazione.

getName() & getModifiers()

Avvolgiamo il nostro array in un elenco per renderlo comodo da usare. Scriveremo anche i metodi 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 il nostro metodo principale , dove chiameremo tutto:


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 ora vediamo tutte le informazioni che vogliamo:

Classe dipendente:
Costruttori:
[private com.codegym.Employee(java.lang.String), public
com.codegym.Employee(java.lang.String,java.lang.String,int), public com.codegym.Employee() ]
Modificatori :
[privato, pubblico, pubblico]

getExceptionTypes()

Questo metodo ci consente di ottenere un array delle eccezioni che il nostro costruttore può generare. Modifichiamo uno dei nostri costruttori e scriviamo un nuovo metodo.

Qui cambiamo leggermente il nostro attuale costruttore:


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

E qui abbiamo un metodo per ottenere i tipi di eccezione e aggiungerlo 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);

Sopra, abbiamo effettuato l'accesso al primo costruttore nel nostro elenco. Discuteremo come ottenere un costruttore specifico un po' più tardi.

E guarda l'output dopo aver aggiunto throws Exception :

Tipi di eccezione:
[classe java.lang.Exception]

E prima di aggiungere l'eccezione:

Tipi di eccezione:
[]

Tutto è meraviglioso, ma come vediamo quali parametri sono richiesti dai nostri costruttori? Scopriamo anche questo.

getParameters() & getParameterTypes() & getGenericParameterTypes()

Ricominciamo affinando il nostro costruttore privato. Ora sarà simile a questo:


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

E abbiamo tre metodi aggiuntivi: getParameters per ottenere l'ordine dei parametri e i relativi tipi, getParameterTypes per ottenere i tipi di parametro e getGenericParameterTypes per ottenere i tipi racchiusi in generics .


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 aggiungiamo qualche informazione in più al nostro metodo principale già non così piccolo:


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

Guardando l'output, vediamo informazioni molto dettagliate sui parametri dei nostri costruttori:

Parametri del costruttore:
[java.lang.String arg0, java.lang.String arg1, java.util.List<java.lang.String> arg2]
Tipi di parametro:
[class java.lang.String, class java.lang.String, interface java.util.List]
Tipi di parametri del costruttore:
[class java.lang.String, class java.lang.String, java.util.List<java.lang.String>]

Ciò dimostra chiaramente la differenza tra ciascun metodo. Vediamo che abbiamo opzioni separate per ottenere informazioni su tipi di parametri, tipi di wrapping e tutto in generale. Super! Ora che conosciamo la classe Constructor , possiamo tornare all'argomento principale del nostro articolo: la creazione di oggetti.

Creazione di un oggetto utilizzando Constructor.newInstance()

Il secondo modo per creare oggetti è chiamare il metodo newInstance sul costruttore. Diamo un'occhiata a un esempio funzionante e vediamo come possiamo ottenere un particolare costruttore.

Se vuoi ottenere un singolo costruttore, dovresti usare il metodo getConstructor (da non confondere con getConstructors , che restituisce un array di tutti i costruttori). Il metodo getConstructor restituisce il costruttore predefinito.


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 vogliamo ottenere un costruttore specifico, dobbiamo passare i tipi di parametro del costruttore a questo metodo.

Non dimenticare che possiamo ottenere il nostro costruttore privato solo utilizzando il metodo getDeclaredConstructor .


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

È così che possiamo ottenere un costruttore specifico. Ora proviamo a creare oggetti utilizzando costruttori privati ​​e pubblici.

Costruttore pubblico:


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

Il risultato è un oggetto con cui possiamo lavorare:

public com.codegym.Employee(java.lang.String,java.lang.String,int)
Impiegato{name='Rob' cognome='Stark', età=10}

Tutto funziona alla grande! Ora proveremo con un costruttore privato:


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

Il risultato è un errore sulla privacy del nostro costruttore:

Java non è riuscito a creare un oggetto utilizzando questo costruttore, ma in realtà c'è qualcosa di magico che possiamo fare nel metodo principale . Possiamo il livello di accesso del nostro costruttore, rendendo possibile la creazione di oggetti della nostra classe:


declaredConstructor.setAccessible(true);

Risultato della creazione di un oggetto

privato com.codegym.Employee(java.lang.String,java.lang.String,java.util.List)
Impiegato{nome='Rob', cognome='Stark', età=-1}

Non impostiamo l'età nel nostro costruttore, quindi rimane uguale a quando è stato inizializzato.

Meraviglioso, riassumiamo!

Vantaggi della creazione di oggetti utilizzando Constructor.newInstance()

Entrambi i metodi condividono lo stesso nome, ma ci sono differenze tra loro:

Classe.nuovaistanza() Constructor.newInstance()
Può chiamare solo un costruttore no-arg . Può chiamare qualsiasi costruttore indipendentemente dal numero di parametri.
Richiede che il costruttore sia visibile. Può anche chiamare costruttori privati ​​in determinate circostanze.
Genera qualsiasi eccezione (controllata o meno) dichiarata dal costruttore. Fa sempre il wrapping di un'eccezione generata con InvocationTargetException .

Per questi motivi, Constructor.newInstance() è preferito a Class.newInstance() , ed è il metodo utilizzato da vari framework e API come Spring, Guava, Zookeeper, Jackson, Servlet, ecc.