Exemplu de creare a unui obiect folosind Class.newInstance()

Imaginează-ți că ești desemnat să creezi un obiect folosind reflexia. Să începem?

Vom începe prin a scrie codul pentru clasa pe care dorim să o instanțiem:


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

Aceasta va fi clasa noastră - cu mai multe câmpuri, un constructor cu parametri, getters și setters, o metodă toString() și un bloc de inițializare. Acum să trecem la a doua parte: crearea unui obiect folosind reflexia. Prima abordare pe care o vom analiza va folosi 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());
    }
}

Excelent! Să rulăm codul nostru și să urmărim ca vârsta să fie afișată. Dar primim o eroare despre un constructor implicit lipsă. Se pare că această metodă ne permite doar să obținem un obiect creat folosind un constructor implicit. Să adăugăm un constructor implicit pentru clasa noastră și să testăm din nou codul.

Mesaj de eroare:

Codul noului constructor


public Employee() { }

După adăugarea constructorului, iată rezultatul:

vârsta este 1

Grozav! Ne-am dat seama cum funcționează această metodă. Acum să aruncăm o privire sub capotă. Deschizând documentația, vedem că metoda noastră este deja depreciată :

De asemenea, poate arunca InstantiationException și IllegalAccessException . În consecință, documentația sugerează să folosim cealaltă modalitate de a crea un obiect, și anume Constructor.newInstance() . Să analizăm în detaliu cum funcționează clasa Constructor .

Metodele getConstructors și getDeclaredConstructors

Pentru a lucra cu clasa Constructor , mai întâi trebuie să obținem o instanță. Avem două metode pentru aceasta: getConstructors și getDeclaredConstructors .

Primul returnează o matrice de constructori publici, iar al doilea returnează o matrice a tuturor constructorilor de clasă.

Să dăm clasei noastre puțină confidențialitate sau, mai degrabă, să creăm niște constructori privați pentru a ne ajuta să demonstrăm cum funcționează aceste metode.

Să adăugăm câțiva constructori privați:


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

Privind codul, rețineți că unul dintre constructori este privat:

Să ne testăm metodele:


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

Și obținem acest rezultat:

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

Bine, așa obținem acces la obiectul Constructor . Acum putem vorbi despre ce poate face.

Clasa java.lang.reflect.Constructor și cele mai importante metode ale acesteia

Să aruncăm o privire la cele mai importante metode și la modul în care funcționează:

Metodă Descriere
getName() Returnează numele acestui constructor ca șir.
getModifiers() Returnează modificatorii de acces Java codificați ca număr.
getExceptionTypes() Returnează o matrice de obiecte Class care reprezintă tipurile de excepții declarate de constructor.
getParameters() Returnează o matrice de obiecte Parameter reprezentând toți parametrii. Returnează o matrice cu lungimea 0 dacă constructorul nu are parametri.
getParameterTypes() Returnează o matrice de obiecte Class care reprezintă tipurile de parametri formali în ordinea declarației.
getGenericParameterTypes() Returnează o matrice de obiecte Type care reprezintă tipurile de parametri formali în ordinea declarației.

getName() și getModifiers()

Să înfășurăm matricea noastră într-o Listă pentru a fi convenabil să lucrezi. Vom scrie, de asemenea, metodele getName și 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;
}

Și metoda noastră principală , unde vom numi totul:


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

Și acum vedem toate informațiile pe care le dorim:

Clasa de angajați:
Constructori :
[private com.codegym.Employee(java.lang.String), public
com.codegym.Employee(java.lang.String,java.lang.String,int), public com.codegym.Employee() ]
Modificatori :
[privat, public, public]

getExceptionTypes()

Această metodă ne permite să obținem o serie de excepții pe care constructorul nostru le poate arunca. Să modificăm unul dintre constructorii noștri și să scriem o nouă metodă.

Aici schimbăm ușor constructorul actual:


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

Și aici avem o metodă pentru a obține tipuri de excepții și pentru a o adăuga la 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);

Mai sus, am accesat primul constructor din lista noastră. Vom discuta cum să obținem un anumit constructor puțin mai târziu.

Și uită-te la ieșire după ce adăugăm aruncări Excepție :

Tipuri de excepții:
[clasa java.lang.Exception]

Și înainte de a adăuga excepția:

Tipuri de excepții:
[]

Totul este minunat, dar cum vedem ce parametri sunt solicitați de constructorii noștri? Să ne dăm seama și de asta.

getParameters() și getParameterTypes() și getGenericParameterTypes()

Să începem din nou prin a perfecționa constructorul nostru privat. Acum va arăta așa:


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

Și avem trei metode suplimentare: getParameters pentru a obține ordinea parametrilor și a tipurilor acestora, getParameterTypes pentru a obține tipurile de parametri și getGenericParameterTypes pentru a obține tipurile împachetate în generice .


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

Și adăugăm mai multe informații la metoda noastră principală , care nu este atât de mică :


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

Privind rezultatul, vedem informații foarte detaliate despre parametrii constructorilor noștri:

Parametri constructor:
[java.lang.String arg0, java.lang.String arg1, java.util.List<java.lang.String> arg2]
Tipuri de parametri:
[clasa java.lang.String, clasa java.lang.String, interfață java.util.List]
Tipuri de parametri constructor:
[clasa java.lang.String, clasa java.lang.String, java.util.List<java.lang.String>]

Acest lucru demonstrează clar diferența dintre fiecare metodă. Vedem că avem opțiuni separate pentru a obține informații despre tipurile de parametri, tipurile împachetate și totul în general. Super! Acum că suntem familiarizați cu clasa Constructor , ne putem întoarce la subiectul principal al articolului nostru - crearea de obiecte.

Crearea unui obiect folosind Constructor.newInstance()

A doua modalitate de a crea obiecte este apelarea metodei newInstance pe constructor. Să aruncăm o privire la un exemplu de lucru și să vedem cum putem obține un anumit constructor.

Dacă doriți să obțineți un singur constructor, ar trebui să utilizați metoda getConstructor (a nu fi confundat cu getConstructors , care returnează o matrice a tuturor constructorilor). Metoda getConstructor returnează constructorul implicit.


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

Și dacă vrem să obținem un anumit constructor, trebuie să trecem tipurile de parametri ale constructorului acestei metode.

Nu uitați că putem obține doar constructorul nostru privat folosind metoda getDeclaredConstructor .


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

Acesta este modul în care putem obține un anumit constructor. Acum să încercăm să creăm obiecte folosind constructori privați și publici.

Constructor public:


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

Rezultatul este un obiect cu care putem lucra:

public com.codegym.Employee(java.lang.String,java.lang.String,int)
Angajat{name='Rob' surname='Stark', varsta=10}

Totul funcționează grozav! Acum vom încerca cu un constructor privat:


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

Rezultatul este o eroare cu privire la confidențialitatea constructorului nostru:

Java nu a putut crea un obiect folosind acest constructor, dar există de fapt ceva magic ce putem face în metoda principală . Putem stabili nivelul de acces al constructorului nostru, făcând posibilă crearea de obiecte din clasa noastră:


declaredConstructor.setAccessible(true);

Rezultatul creării unui obiect

privat com.codegym.Employee(java.lang.String,java.lang.String,java.util.List)
Angajat{name='Rob', surname='Stark', age=-1}

Nu setăm vârsta în constructorul nostru, așa că rămâne la fel ca atunci când a fost inițializat.

Minunat, să rezumam!

Beneficiile creării de obiecte folosind Constructor.newInstance()

Ambele metode au același nume, dar există diferențe între ele:

Class.newInstance() Constructor.newInstance()
Poate apela doar un constructor fără argument . Poate apela orice constructor indiferent de numărul de parametri.
Necesită ca constructorul să fie vizibil. Poate apela și constructori privați în anumite circumstanțe.
Aruncă orice excepție (bifată sau nu) care este declarată de constructor. Întotdeauna include o excepție aruncată cu InvocationTargetException .

Din aceste motive, Constructor.newInstance() este preferat față de Class.newInstance() și este metoda folosită de diverse cadre și API-uri precum Spring, Guava, Zookeeper, Jackson, Servlet etc.