Przykład tworzenia obiektu za pomocą Class.newInstance()

Wyobraź sobie: stoisz przed zadaniem: musisz stworzyć obiekt za pomocą odbicia. Zaczynamy?

Zacznijmy od początkowej klasy, którą chcemy uzyskać:

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

To będzie nasza klasa, w której jest kilka pól, konstruktor z parametrami, gettery i settery, metoda toString() i blok inicjalizacyjny. Przejdźmy teraz do drugiej części - tworzenia obiektu za pomocą odbicia. Pierwszym sposobem, któremu się przyjrzymy, będzie implementacja za pomocą 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());
    }
}

Świetnie, uruchommy nasz kod i poczekajmy na dane wyjściowe wieku z inicjatora. Ale otrzymujemy błąd o braku domyślnego konstruktora. Okazuje się, że tą metodą możemy otrzymać jedynie obiekt utworzony za pomocą domyślnego konstruktora. Dodajmy domyślny konstruktor dla naszej klasy i ponownie przetestujmy kod.

Komunikat o błędzie:

Nowy kod konstruktora:

public Employee() { }

Wynik działania programu po dodaniu konstruktora:

wiek wynosi - -1

Świetnie, odkryliśmy, jak działa ta metoda. Teraz zajrzyjmy pod maskę. Otwieramy dokumentację i widzimy, że nasza metoda jest już przestarzała ( Angielski - przestarzały ):

Może również rzucić InstantiationException , IllegalAccessException . Dlatego dokumentacja sugeruje, abyśmy skorzystali z drugiego sposobu tworzenia obiektu, a mianowicie Constructor.newInstance() . Przeanalizujmy działanie klasy Constructor i porozmawiajmy o tym bardziej szczegółowo.

metody getConstructors i getDeclaredConstructors

Aby pracować z klasą Constructor , najpierw musimy ją zdobyć. Mamy do tego dwie metody: getConstructors i getDeclaredConstructors .

Pierwsza zwraca listę publicznych konstruktorów jako tablicę, podczas gdy druga zwraca listę wszystkich konstruktorów klas jako tablicę.

Dodajmy trochę prywatności do naszej klasy, a mianowicie stworzymy kilka prywatnych konstruktorów, aby wyświetlić działanie naszych metod.

Dodaj kilku prywatnych konstruktorów:

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

W strukturze naszej klasy widać wyraźnie, że jeden z konstruktorów jest prywatny:

Testowanie naszych metod:

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 otrzymujemy taki wynik:

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 en.codegym.Employee(java.lang.String,java.lang.String,int)
public en.codegym.Employee()

Świetnie, więc mamy dostęp do klasy Constructor . Teraz możemy mówić o jego możliwościach.

Klasa java.lang.reflect.Constructor, jej główne metody

Przyjrzyjmy się głównym metodom i ich działaniu:

metoda Opis
pobierzNazwę() Zwraca nazwę tego konstruktora jako ciąg znaków.
getModifiers() Zwraca modyfikatory języka Java jako liczbę.
getExceptionType() Zwraca tablicę obiektów klasy reprezentujących typy wyjątków zadeklarowanych przez konstruktora.
getParameters() Zwraca tablicę obiektów Parameter reprezentujących wszystkie parametry. Zwraca tablicę o długości 0, jeśli konstruktor nie ma parametrów.
getParameterTypes() Zwraca tablicę obiektów klas reprezentujących formalne typy parametrów w kolejności deklaracji.
getGenericParameterTypes() Zwraca tablicę obiektów typu reprezentujących formalne typy parametrów w kolejności deklaracji.

getName() & getModifiers()

Zapakujmy naszą tablicę w Listę, aby ułatwić nam pracę, i napiszmy metody 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 nasza główna metoda , w której będziemy nazywać wszystko:

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

W rezultacie zobaczymy wszystkie potrzebne nam informacje:

Klasa pracownika:
Konstruktory:
[private com.codegym.Employee(java.lang.String), public
com.codegym.Employee(java.lang.String,java.lang.String,int), public com.codegym.Employee() ]
Modyfikatory :
[prywatny, publiczny, publiczny]

getExceptionType()

Ta metoda pozwala nam uzyskać tablicę wyjątków, które może zgłosić nasz konstruktor. Zmodyfikujmy jeden z naszych konstruktorów i napiszmy nową metodę.

Tutaj nieznacznie zmodyfikowaliśmy naszego obecnego prywatnego konstruktora:

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

A tutaj mamy metodę uzyskiwania typów wyjątków i dodawania ich do 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);

Powyżej zwróciliśmy się do pierwszego konstruktora z naszej listy. Jak zdobyć konkretnego konstruktora, omówimy nieco później.

I spójrz na dane wyjściowe po dodaniu throws Exception :

Typy wyjątków:
[klasa java.lang.Exception]

A przed dodaniem wyjątku:

Typy wyjątków:
[]

Wszystko fajnie tylko jak sprawdzić jakie parametry są potrzebne naszym konstruktorom? Przyjrzyjmy się tej części.

getParameters() & getParameterTypes() & getGenericParameterTypes()

Zacznijmy jeszcze raz od dopracowania naszego prywatnego konstruktora. Teraz będzie to wyglądać tak:

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

Mamy też trzy dodatkowe metody: getParameters do pobrania kolejności parametrów i ich typów, getParameterTypes do pobrania typów parametrów oraz getGenericParameterTypes do pobrania typów opakowanych w generyczne .

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

A w naszej już nie tak małej głównej dodajemy jeszcze kilka informacji:

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

Jeśli spojrzymy na nasz wynik, otrzymamy bardzo szczegółowe dane o parametrach naszych konstruktorów:

Parametry konstruktora:
[java.lang.String arg0, java.lang.String arg1, java.util.List<java.lang.String> arg2]
Typy parametrów:
[class java.lang.String, class java.lang.String, interfejs java.util.List]
Typy parametrów konstruktora:
[class java.lang.String, class java.lang.String, java.util.List<java.lang.String>]

Tutaj możesz wyraźnie zobaczyć różnicę między każdą metodą. Widzimy, że można osobno uzyskać dane o typach parametrów, dane o klasach opakowań i ogólnie o wszystkim. Super! Zakończyliśmy naszą znajomość z klasą Constructor i teraz możemy wrócić do głównego tematu naszego artykułu - tworzenia obiektów.

Tworzenie obiektu za pomocą Constructor.newInstance()

Drugim sposobem tworzenia obiektów jest wywołanie metody newInstance na konstruktorze. Rzućmy okiem na działający przykład i zobaczmy, w jaki sposób otrzymujemy konkretnego konstruktora.

Jeśli Twoim zadaniem jest uzyskanie jednego konstruktora, musisz użyć metody getConstructor (nie mylić z metodą getConstructors , która zwraca tablicę wszystkich konstruktorów). Metoda getConstructor zwraca domyślny konstruktor.

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

A jeśli chcemy uzyskać konkretny konstruktor, musimy przekazać tej metodzie typy parametrów, które będą w konstruktorze.

Nie zapominajmy, że możemy uzyskać naszego prywatnego konstruktora tylko za pomocą metody getDeclaredConstructor .

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

W ten sposób możemy otrzymać konkretnego konstruktora. Spróbujmy teraz utworzyć obiekt z prywatnego i publicznego konstruktora.

Konstruktor publiczny:

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

W rezultacie mamy obiekt, z którym możemy dalej pracować:

public en.codegym.Employee(java.lang.String,java.lang.String,int)
Pracownik{imię='NeIvan' nazwisko='NeIvanov', wiek=10}

Wszystko działa świetnie! Teraz próbujemy z prywatnym konstruktorem:

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

W rezultacie otrzymamy błąd dotyczący prywatności naszego konstruktora:

Java nie mogła utworzyć obiektu za pomocą tego konstruktora, ale w metodzie main można zrobić trochę magii . Ustawi dostępność dla naszego konstruktora i pozwoli na tworzenie obiektów naszej klasy:

declaredConstructor.setAccessible(true);

Wynik utworzenia obiektu:

private en.codegym.Employee(java.lang.String,java.lang.String,java.util.List)
Pracownik{imię='NeIvan', nazwisko='NeIvanov', wiek=-1}

Nie ustawiamy wieku w naszym konstruktorze i pozostaje on taki sam, jak podczas inicjalizacji.

Super, czas na podsumowanie!

Korzyści z tworzenia za pomocą Constructor.newInstance()

Obie metody wyglądają podobnie w nazwie, ale istnieją między nimi różnice:

Class.newInstance() Konstruktor. nowaInstancja()
Może wywoływać tylko konstruktora no-arg . Może wywołać dowolnego konstruktora niezależnie od liczby parametrów.
Wymaga, aby konstruktor był widoczny. W pewnych okolicznościach może również wywoływać prywatnych konstruktorów.
Zgłasza każdy wyjątek (zaznaczony lub nie) zadeklarowany przez konstruktora. Zawsze otacza zgłoszony wyjątek InvocationTargetException .

Z tych powodów metoda Constructor.newInstance() jest preferowana w porównaniu z Class.newInstance() i jest używana przez różne frameworki i interfejsy API, takie jak Spring, Guava, Zookeeper, Jackson, Servlet itp.