Пример за създаване на обект с помощта на Class.newInstance()

Представете си, че ви е възложено да създадете обект с помощта на отражение. Да започваме ли?

Ще започнем с написването на codeа за класа, който искаме да създадем:


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

Това ще бъде нашият клас — с няколко полета, конструктор с параметри, гетери и сетери, метод toString() и блок за инициализация. Сега нека преминем към втората част: създаване на обект с помощта на отражение. Първият подход, който ще разгледаме, ще използва 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());
    }
}

Отлично! Нека изпълним нашия code и да гледаме възрастта да се покаже. Но получаваме грешка за липсващ конструктор по подразбиране. Оказва се, че този метод ни позволява само да получим обект, създаден с помощта на конструктор по подразбиране. Нека добавим конструктор по подразбиране за нашия клас и отново да тестваме codeа.

Съобщение за грешка:

Код на новия конструктор


public Employee() { }

След добавяне на конструктора, ето изхода:

възрастта е 1

Страхотен! Разбрахме How работи този метод. Сега нека надникнем под капака. Отваряйки documentацията, виждаме, че нашият метод вече е отхвърлен :

Може също да хвърля InstantiationException и IllegalAccessException . Съответно, documentацията предлага да използваме другия начин за създаване на обект, а именно Constructor.newInstance() . Нека анализираме подробно How работи класът Constructor .

методи getConstructors и getDeclaredConstructors

За да работим с класа Constructor , първо трябва да получим екземпляр. Имаме два метода за това: getConstructors и getDeclaredConstructors .

Първият връща масив от публични конструктори, а вторият връща масив от всички конструктори на класове.

Нека да дадем малко поверителност на нашия клас, or по-скоро, нека създадем частни конструктори, които да ни помогнат да демонстрираме How работят тези методи.

Нека добавим някои частни конструктори:


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

Разглеждайки codeа, имайте предвид, че един от конструкторите е частен:

Нека тестваме нашите методи:


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

И получаваме този резултат:

getConstructors:
публичен com.codegym.Employee(java.lang.String,java.lang.String,int)
public.com.codegym.Employee()

getDeclaredConstructors:
частен com.codegym.Employee(java.lang.String,java.lang .String)
public com.codegym.Employee(java.lang.String,java.lang.String,int)
public com.codegym.Employee()

Добре, така получаваме достъп до обекта Constructor . Сега можем да говорим за това Howво може да направи.

Класът java.lang.reflect.Constructor и неговите най-важни методи

Нека да разгледаме най-важните методи и How работят:

Метод Описание
getName() Връща името на този конструктор като низ.
getModifiers() Връща модификаторите за достъп на Java, codeирани като число.
getExceptionTypes() Връща масив от Class обекти, които представляват типовете изключения, декларирани от конструктора.
getParameters() Връща масив от обекти Parameter , представящи всички параметри. Връща масив с дължина 0, ако конструкторът няма параметри.
getParameterTypes() Връща масив от Class обекти, които представляват формалните типове параметри в реда на деклариране.
getGenericParameterTypes() Връща масив от обекти тип, които представляват формалните типове параметри в реда на деклариране.

getName() & getModifiers()

Нека увием нашия масив в списък , за да го направим удобен за работа. Ще напишем също методи getName и 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;
}

И основният ни метод, където ще извикаме всичко:


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

И сега виждаме цялата информация, която искаме:

Клас служител:
Конструктори:
[private com.codegym.Employee(java.lang.String), public
com.codegym.Employee(java.lang.String,java.lang.String,int), public com.codegym.Employee() ]
Модификатори:
[частен, публичен, обществен]

getExceptionTypes()

Този метод ни позволява да получим масив от изключения, които нашият конструктор може да хвърли. Нека модифицираме един от нашите конструктори и да напишем нов метод.

Тук променяме леко текущия ни конструктор:


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

И тук имаме метод за получаване на типове изключения и добавянето му към 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);

По-горе получихме достъп до първия конструктор в нашия списък. Ще обсъдим How да получите конкретен конструктор малко по-късно.

И вижте изхода, след като добавим throws Exception :

Типове изключения:
[клас java.lang.Exception]

И преди да добавите изключението:

Типове изключения:
[]

Всичко е прекрасно, но How да видим Howви параметри изискват нашите конструктори? Нека да разберем и това.

getParameters() & getParameterTypes() & getGenericParameterTypes()

Нека започнем отново, като прецизираме нашия частен конструктор. Сега ще изглежда така:


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

Имаме и три допълнителни метода: getParameters за получаване на реда на параметрите и техните типове, getParameterTypes за получаване на типовете параметри и getGenericParameterTypes за получаване на типове, обвити в генерични .


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

И ние добавяме още малко информация към нашия вече не толкова малък основен метод:


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

Разглеждайки изхода, виждаме много подробна информация за параметрите на нашите конструктори:

Параметри на конструктора:
[java.lang.String arg0, java.lang.String arg1, java.util.List<java.lang.String> arg2]
Типове параметри:
[клас java.lang.String, клас java.lang.String, интерфейс java.util.List]
Типове параметри на конструктора:
[клас java.lang.String, клас java.lang.String, java.util.List<java.lang.String>]

Това ясно показва разликата между всеки метод. Виждаме, че имаме отделни опции за получаване на информация за типове параметри, обвити типове и всичко като цяло. Супер! Сега, след като се запознахме с класа Constructor , можем да се върнем към основната тема на нашата статия - създаване на обекти.

Създаване на обект с помощта на Constructor.newInstance()

Вторият начин за създаване на обекти е да се извика методът newInstance на конструктора. Нека да разгледаме работещ пример и да видим How можем да получим определен конструктор.

Ако искате да получите един конструктор, трябва да използвате метода getConstructor (да не се бърка с getConstructors , който връща масив от всички конструктори). Методът getConstructor връща конструктора по подразбиране.


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

И ако искаме да получим конкретен конструктор, трябва да предадем типовете параметри на конструктора към този метод.

Не забравяйте, че можем да получим нашия частен конструктор само чрез метода getDeclaredConstructor .


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

Така можем да получим конкретен конструктор. Сега нека се опитаме да създадем обекти, използвайки частни и публични конструктори.

Публичен конструктор:


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

Резултатът е обект, с който можем да работим:

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

Всичко работи отлично! Сега ще опитаме с частен конструктор:


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

Резултатът е грешка относно поверителността на нашия конструктор:

Java не можа да създаде обект с помощта на този конструктор, но всъщност има нещо магическо, което можем да направим в основния метод. Ние можем да определим нивото на достъп на нашия конструктор, което прави възможно създаването на обекти от нашия клас:


declaredConstructor.setAccessible(true);

Резултат от създаването на обект

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

Ние не задаваме възрастта в нашия конструктор, така че той остава същият, Howто когато е бил инициализиран.

Прекрасно, нека обобщим!

Предимства от създаването на обекти с помощта на Constructor.newInstance()

И двата метода имат едно и също име, но има разлики между тях:

Class.newInstance() Constructor.newInstance()
Може да извика само конструктор без аргументи . Може да извика всеки конструктор, независимо от броя на параметрите.
Изисква конструкторът да бъде видим. Може да се обади и на частни конструктори при определени обстоятелства.
Изхвърля всяко изключение (проверено or не), което е декларирано от конструктора. Винаги обвива хвърлено изключение с InvocationTargetException .

Поради тези причини Constructor.newInstance() се предпочита пред Class.newInstance() и е методът, използван от различни рамки и API като Spring, Guava, Zookeeper, Jackson, Servlet и др.