Beispiel für die Erstellung eines Objekts mit Class.newInstance()

Stellen Sie sich vor, Sie sollen mithilfe von Reflexion ein Objekt erstellen. Sollen wir anfangen?

Wir beginnen damit, den Code für die Klasse zu schreiben, die wir instanziieren möchten:


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

Dies wird unsere Klasse sein – mit mehreren Feldern, einem Konstruktor mit Parametern, Gettern und Settern, einer toString()- Methode und einem Initialisierungsblock. Kommen wir nun zum zweiten Teil: Erstellen eines Objekts mithilfe von Reflexion. Der erste Ansatz, den wir betrachten werden, wird Class.newInstance() verwenden .


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

Exzellent! Lassen Sie uns unseren Code ausführen und darauf achten, dass das Alter angezeigt wird. Wir erhalten jedoch eine Fehlermeldung über einen fehlenden Standardkonstruktor. Es stellt sich heraus, dass wir mit dieser Methode nur ein Objekt erhalten können, das mit einem Standardkonstruktor erstellt wurde. Fügen wir einen Standardkonstruktor für unsere Klasse hinzu und testen den Code erneut.

Fehlermeldung:

Code des neuen Konstruktors


public Employee() { }

Nach dem Hinzufügen des Konstruktors ist hier die Ausgabe:

Alter ist 1

Großartig! Wir haben herausgefunden, wie diese Methode funktioniert. Werfen wir nun einen Blick unter die Haube. Wenn wir die Dokumentation aufschlagen, sehen wir, dass unsere Methode bereits veraltet ist :

Es kann auch InstantiationException und IllegalAccessException auslösen . Dementsprechend schlägt die Dokumentation vor, dass wir die andere Möglichkeit zum Erstellen eines Objekts verwenden, nämlich Constructor.newInstance() . Lassen Sie uns im Detail analysieren, wie die Constructor- Klasse funktioniert.

getConstructors- und getDeclaredConstructors-Methoden

Um mit der Constructor- Klasse arbeiten zu können, müssen wir zunächst eine Instanz abrufen. Wir haben dafür zwei Methoden: getConstructors und getDeclaredConstructors .

Der erste gibt ein Array öffentlicher Konstruktoren zurück und der zweite gibt ein Array aller Klassenkonstruktoren zurück.

Geben wir unserer Klasse etwas Privatsphäre, oder besser gesagt, erstellen wir einige private Konstruktoren, um zu demonstrieren, wie diese Methoden funktionieren.

Fügen wir einige private Konstruktoren hinzu:


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

Beachten Sie beim Betrachten des Codes, dass einer der Konstruktoren privat ist:

Testen wir unsere Methoden:


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

Und wir erhalten dieses Ergebnis:

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

Okay, so erhalten wir Zugriff auf das Constructor- Objekt. Jetzt können wir darüber reden, was es kann.

Die Klasse java.lang.reflect.Constructor und ihre wichtigsten Methoden

Werfen wir einen Blick auf die wichtigsten Methoden und ihre Funktionsweise:

Methode Beschreibung
getName() Gibt den Namen dieses Konstruktors als Zeichenfolge zurück.
getModifiers() Gibt die als Zahl codierten Java-Zugriffsmodifikatoren zurück.
getExceptionTypes() Gibt ein Array von Class-Objekten zurück, die die vom Konstruktor deklarierten Ausnahmetypen darstellen.
getParameters() Gibt ein Array von Parameterobjekten zurück, die alle Parameter darstellen. Gibt ein Array der Länge 0 zurück, wenn der Konstruktor keine Parameter hat.
getParameterTypes() Gibt ein Array von Klassenobjekten zurück, die die formalen Parametertypen in der Deklarationsreihenfolge darstellen.
getGenericParameterTypes() Gibt ein Array von Type-Objekten zurück, die die formalen Parametertypen in der Deklarationsreihenfolge darstellen.

getName() & getModifiers()

Um die Arbeit damit zu vereinfachen, packen wir unser Array in eine Liste . Wir schreiben auch die Methoden getName und 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;
}

Und unsere Hauptmethode , bei der wir alles aufrufen:


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

Und jetzt sehen wir alle Informationen, die wir wollen:

Mitarbeiterklasse:
Konstruktoren:
[private com.codegym.Employee(java.lang.String), public
com.codegym.Employee(java.lang.String,java.lang.String,int), public com.codegym.Employee() ]
Modifikatoren:
[privat, öffentlich, öffentlich]

getExceptionTypes()

Mit dieser Methode können wir ein Array der Ausnahmen abrufen, die unser Konstruktor möglicherweise auslöst. Lassen Sie uns einen unserer Konstruktoren ändern und eine neue Methode schreiben.

Hier ändern wir unseren aktuellen Konstruktor leicht:


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

Und hier haben wir eine Methode zum Abrufen von Ausnahmetypen und zum Hinzufügen zu 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);

Oben haben wir auf den ersten Konstruktor in unserer Liste zugegriffen. Wir werden etwas später besprechen, wie man einen bestimmten Konstruktor erhält.

Und schauen Sie sich die Ausgabe an, nachdem wir throws Exception hinzugefügt haben :

Ausnahmetypen:
[Klasse java.lang.Exception]

Und bevor Sie die Ausnahme hinzufügen:

Ausnahmetypen:
[]

Alles ist wunderbar, aber wie sehen wir, welche Parameter unsere Konstrukteure benötigen? Lassen Sie uns das auch herausfinden.

getParameters() & getParameterTypes() & getGenericParameterTypes()

Beginnen wir noch einmal mit der Verfeinerung unseres privaten Konstruktors. Jetzt wird es so aussehen:


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

Und wir haben drei zusätzliche Methoden: getParameters zum Abrufen der Reihenfolge der Parameter und ihrer Typen, getParameterTypes zum Abrufen der Parametertypen und getGenericParameterTypes zum Abrufen von in Generics eingeschlossenen Typen .


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

Und wir ergänzen unsere ohnehin schon gar nicht so kleine Hauptmethode noch um einige weitere Informationen :


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

Wenn wir uns die Ausgabe ansehen, sehen wir sehr detaillierte Informationen zu den Parametern unserer Konstruktoren:

Konstruktorparameter:
[java.lang.String arg0, java.lang.String arg1, java.util.List<java.lang.String> arg2]
Parametertypen:
[Klasse java.lang.String, Klasse java.lang.String, Schnittstelle java.util.List]
Konstruktorparametertypen:
[Klasse java.lang.String, Klasse java.lang.String, java.util.List<java.lang.String>]

Dies zeigt deutlich den Unterschied zwischen den einzelnen Methoden. Wir sehen, dass wir separate Optionen haben, um Informationen zu Parametertypen, umschlossenen Typen und allem im Allgemeinen zu erhalten. Super! Nachdem wir nun mit der Constructor -Klasse vertraut sind, können wir zum Hauptthema unseres Artikels zurückkehren – dem Erstellen von Objekten.

Erstellen eines Objekts mit Constructor.newInstance()

Die zweite Möglichkeit, Objekte zu erstellen, besteht darin, die newInstance- Methode im Konstruktor aufzurufen . Schauen wir uns ein funktionierendes Beispiel an und sehen wir, wie wir einen bestimmten Konstruktor erhalten können.

Wenn Sie einen einzelnen Konstruktor erhalten möchten, sollten Sie die Methode getConstructor verwenden (nicht zu verwechseln mit getConstructors , die ein Array aller Konstruktoren zurückgibt). Die getConstructor- Methode gibt den Standardkonstruktor zurück.


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

Und wenn wir einen bestimmten Konstruktor erhalten möchten, müssen wir die Parametertypen des Konstruktors an diese Methode übergeben.

Vergessen Sie nicht, dass wir unseren privaten Konstruktor nur mit der Methode getDeclaredConstructor abrufen können .


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

Auf diese Weise können wir einen bestimmten Konstruktor erhalten. Versuchen wir nun, Objekte mit privaten und öffentlichen Konstruktoren zu erstellen.

Öffentlicher Konstruktor:


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

Das Ergebnis ist ein Objekt, mit dem wir arbeiten können:

public com.codegym.Employee(java.lang.String,java.lang.String,int)
Mitarbeiter{name='Rob' Nachname='Stark', Alter=10}

Alles funktioniert super! Jetzt versuchen wir es mit einem privaten Konstruktor:


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

Das Ergebnis ist ein Fehler bezüglich der Privatsphäre unseres Konstruktors:

Java konnte mit diesem Konstruktor kein Objekt erstellen, aber mit der Hauptmethode können wir tatsächlich etwas Magisches tun . Wir können die Zugriffsebene unseres Konstruktors festlegen, wodurch es möglich wird, Objekte unserer Klasse zu erstellen:


declaredConstructor.setAccessible(true);

Ergebnis der Erstellung eines Objekts

privat com.codegym.Employee(java.lang.String,java.lang.String,java.util.List)
Mitarbeiter{name='Rob', Nachname='Stark', Alter=-1}

Wir legen das Alter in unserem Konstruktor nicht fest, daher bleibt es dasselbe wie bei der Initialisierung.

Wunderbar, fassen wir zusammen!

Vorteile der Objekterstellung mit Constructor.newInstance()

Beide Methoden tragen den gleichen Namen, es gibt jedoch Unterschiede zwischen ihnen:

Class.newInstance() Constructor.newInstance()
Kann nur einen Konstruktor ohne Argumente aufrufen . Kann jeden Konstruktor aufrufen, unabhängig von der Anzahl der Parameter.
Erfordert, dass der Konstruktor sichtbar ist. Kann unter bestimmten Umständen auch private Konstruktoren aufrufen.
Löst jede Ausnahme aus (aktiviert oder nicht), die vom Konstruktor deklariert wird. Umschließt eine ausgelöste Ausnahme immer mit InvocationTargetException .

Aus diesen Gründen wird Constructor.newInstance() gegenüber Class.newInstance() bevorzugt und ist die Methode, die von verschiedenen Frameworks und APIs wie Spring, Guava, Zookeeper, Jackson, Servlet usw. verwendet wird.