Eksempel på å lage et objekt ved å bruke Class.newInstance()

Tenk deg at du får i oppdrag å lage et objekt ved hjelp av refleksjon. Skal vi sette i gang?

Vi starter med å skrive koden for klassen vi ønsker å instansiere:


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

Dette vil være vår klasse - med flere felt, en konstruktør med parametere, gettere og settere, en toString()- metode og en initialiseringsblokk. La oss nå gå videre til den andre delen: å lage et objekt ved hjelp av refleksjon. Den første tilnærmingen vi skal se på vil bruke 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());
    }
}

Utmerket! La oss kjøre koden vår og se etter alderen som skal vises. Men vi får en feilmelding om en manglende standardkonstruktør. Det viser seg at denne metoden bare lar oss få et objekt opprettet ved hjelp av en standard konstruktør. La oss legge til en standardkonstruktør for klassen vår og teste koden på nytt.

Feilmelding:

Koden til den nye konstruktøren


public Employee() { }

Etter å ha lagt til konstruktøren, her er utgangen:

alderen er 1

Flott! Vi fant ut hvordan denne metoden fungerer. La oss nå ta en titt under panseret. Når vi åpner dokumentasjonen, ser vi at metoden vår allerede er utdatert :

Det kan også kaste InstantiationException og IllegalAccessException . Følgelig foreslår dokumentasjonen at vi bruker den andre måten å lage et objekt på, nemlig Constructor.newInstance() . La oss analysere i detalj hvordan Constructor- klassen fungerer.

getConstructors og getDeclaredConstructors metoder

For å jobbe med Constructor- klassen må vi først få en forekomst. Vi har to metoder for dette: getConstructors og getDeclaredConstructors .

Den første returnerer en rekke offentlige konstruktører, og den andre returnerer en matrise med alle klassekonstruktører.

La oss gi klassen litt privatliv, eller rettere sagt, la oss lage noen private konstruktører for å demonstrere hvordan disse metodene fungerer.

La oss legge til noen private konstruktører:


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

Når du ser på koden, merk at en av konstruktørene er privat:

La oss teste metodene våre:


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

Og vi får dette resultatet:

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, dette er hvordan vi får tilgang til Constructor- objektet. Nå kan vi snakke om hva den kan gjøre.

Java.lang.reflect.Constructor-klassen og dens viktigste metoder

La oss ta en titt på de viktigste metodene og hvordan de fungerer:

Metode Beskrivelse
getName() Returnerer navnet på denne konstruktøren som en streng.
getModifiers() Returnerer Java-tilgangsmodifikatorene kodet som et tall.
getExceptionTypes() Returnerer en rekke klasseobjekter som representerer typene unntak som er deklarert av konstruktøren.
getParameters() Returnerer en rekke parameterobjekter som representerer alle parameterne. Returnerer en matrise med lengde 0 hvis konstruktøren ikke har noen parametere.
getParameterTypes() Returnerer en rekke klasseobjekter som representerer de formelle parametertypene i deklarasjonsrekkefølge.
getGenericParameterTypes() Returnerer en matrise med Type-objekter som representerer de formelle parametertypene i deklarasjonsrekkefølge.

getName() og getModifiers()

La oss pakke matrisen inn i en liste for å gjøre den praktisk å jobbe med. Vi vil også skrive getName og getModifiers metoder:


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

Og vår hovedmetode , hvor vi vil kalle alt:


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

Og nå ser vi all informasjonen vi ønsker:

Ansattklasse:
Konstruktører:
[privat com.codegym.Employee(java.lang.String), offentlig
com.codegym.Employee(java.lang.String,java.lang.String,int), offentlig com.codegym.Employee() ]
Modifikatorer:
[privat, offentlig, offentlig]

getExceptionTypes()

Denne metoden lar oss få en rekke unntak som konstruktøren vår kan kaste. La oss modifisere en av konstruktørene våre og skrive en ny metode.

Her endrer vi vår nåværende konstruktør litt:


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

Og her har vi en metode for å få unntakstyper og legge den til hoved :


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

Ovenfor fikk vi tilgang til den første konstruktøren i listen vår. Vi vil diskutere hvordan du får en spesifikk konstruktør litt senere.

Og se på utgangen etter at vi har lagt til kast Unntak :

Unntakstyper :
[class java.lang.Exception]

Og før du legger til unntaket:

Unntakstyper:
[]

Alt er fantastisk, men hvordan ser vi hvilke parametere som kreves av konstruktørene våre? La oss finne ut av dette også.

getParameters() & getParameterTypes() & getGenericParameterTypes()

La oss starte på nytt med å foredle vår private konstruktør. Nå vil det se slik ut:


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

Og vi har tre ekstra metoder: getParameters for å få rekkefølgen på parametere og deres typer, getParameterTypes for å få parametertypene, og getGenericParameterTypes for å få typer pakket inn i generiske artikler .


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

Og vi legger til litt mer informasjon til vår allerede ikke så lille hovedmetode :


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

Når vi ser på utdataene, ser vi veldig detaljert informasjon om parametrene til konstruktørene våre:

Konstruktørparametere :
[java.lang.String arg0, java.lang.String arg1, java.util.List<java.lang.String> arg2]
Parametertyper :
[class java.lang.String, class java.lang.String, grensesnitt java.util.List]
Konstruktør-parametertyper :
[class java.lang.String, class java.lang.String, java.util.List<java.lang.String>]

Dette viser tydelig forskjellen mellom hver metode. Vi ser at vi har separate alternativer for å få informasjon om parametertyper, innpakket typer og alt generelt. Super! Nå som vi er kjent med Constructor- klassen, kan vi gå tilbake til hovedemnet i artikkelen vår - å lage objekter.

Opprette et objekt ved å bruke Constructor.newInstance()

Den andre måten å lage objekter på er å kalle newInstance -metoden på konstruktøren. La oss ta en titt på et fungerende eksempel og se hvordan vi kan få en bestemt konstruktør.

Hvis du ønsker å få en enkelt konstruktør, bør du bruke getConstructor- metoden (må ikke forveksles med getConstructors , som returnerer en matrise av alle konstruktørene). GetConstructor - metoden returnerer standardkonstruktøren.


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

Og hvis vi ønsker å få en spesifikk konstruktør, må vi overføre konstruktørens parametertyper til denne metoden.

Ikke glem at vi bare kan få vår private konstruktør ved å bruke getDeclaredConstructor -metoden.


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

Slik kan vi få en spesifikk konstruktør. La oss nå prøve å lage objekter ved hjelp av private og offentlige konstruktører.

Offentlig konstruktør:


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

Resultatet er et objekt som vi kan jobbe med:

offentlig com.codegym.Employee(java.lang.String,java.lang.String,int)
Ansatt{name='Rob' surname='Stark', age=10}

Alt fungerer utmerket! Nå skal vi prøve med en privat konstruktør:


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

Resultatet er en feil om personvernet til konstruktøren vår:

Java kunne ikke lage et objekt ved å bruke denne konstruktøren, men det er faktisk noe magisk vi kan gjøre i hovedmetoden . Vi kan konstruktørens tilgangsnivå, noe som gjør det mulig å lage objekter av klassen vår:


declaredConstructor.setAccessible(true);

Resultat av å lage et objekt

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

Vi angir ikke alderen i konstruktøren vår, så den forblir den samme som da den ble initialisert.

Fantastisk, la oss oppsummere!

Fordeler med å lage objekter ved å bruke Constructor.newInstance()

Begge metodene har samme navn, men det er forskjeller mellom dem:

Class.newInstance() Constructor.newInstance()
Kan bare kalle en no-arg konstruktør. Kan kalle hvilken som helst konstruktør uavhengig av antall parametere.
Krever at konstruktøren er synlig. Kan også ringe private konstruktører under visse omstendigheter.
Kaster ethvert unntak (avmerket eller ikke) som er deklarert av konstruktøren. Omslutter alltid et kastet unntak med InvocationTargetException .

Av disse grunnene foretrekkes Constructor.newInstance() fremfor Class.newInstance() , og er metoden som brukes av ulike rammeverk og APIer som Spring, Guava, Zookeeper, Jackson, Servlet, etc.