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

Представь: перед тобой встала задача: нужно создать объект, используя рефлексию. Приступаем?

Начнем с начального класса, который мы хотим получить:


public class Employee {
    private String name;
    private String surname;
    private int age;

    {
        age = -1;
        name = "Ivan";
        surname = "Ivanov";
    }

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

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

Отлично, запускаем наш код и ждем вывода возраста из инициализатора. Но получаем ошибку об отсутствии конструктора по умолчанию. Получается, с помощью этого метода мы можем получить только объект, созданный с помощью конструктора по умолчанию. Давайте добавим конструктор по умолчанию для нашего класса и протестируем код еще раз.

Сообщение об ошибке:

Код нового конструктора:


public Employee() { }

Результат программы после добавления конструктора:

age is - -1

Отлично, мы разобрались, как работает этот метод. Теперь давай заглянем к нему под капот. Открываем документацию и видим, что наш метод уже deprecated (англ. — устаревший):

А еще может выбросить исключения InstantiationException, IllegalAccessException. Поэтому в документации нам предлагают использовать второй способ создания объекта, а именно Constructor.newInstance(). Давай разберем работу класса Constructor и поговорим о нем подробнее.

Методы getConstructors и getDeclaredConstructors

Чтобы работать с классом Constructor, нам нужно сначала получить его. Для этого у нас есть два метода: getConstructors и getDeclaredConstructors.

Первый возвращает список публичных конструкторов в виде массива, а второй возвращает список всех конструкторов класса в виде массива.

Давай добавим немного приватности нашему классу, а именно сделаем несколько приватных конструкторов для отображения работы наших методов.

Добавляем немного приватных конструкторов:


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

В структуре нашего класса отлично видно, что один из конструкторов приватный:

Тестируем работу наших методов:


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:
public ru.javarush.Employee(java.lang.String,java.lang.String,int)
public.ru.javarush.Employee()

getDeclaredConstructors:
private ru.javarush.Employee(java.lang.String,java.lang.String)
public ru.javarush.Employee(java.lang.String,java.lang.String,int)
public ru.javarush.Employee()

Отлично, таким образом мы получаем доступ к классу Constructor. Теперь можем поговорить о его возможностях.

Класс java.lang.reflect.Constructor, его основные методы

Давай взглянем на основные методы и как они работают:

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

getName() & getModifiers()

Давай обернем наш массив в List, чтобы нам было удобно работать, и напишем метод 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;
}

И наш метод main, где мы будем все вызывать:


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:");
    System.out.println("Конструкторы :");
    System.out.println(constructorNames);
    System.out.println("Модификаторы :");
    System.out.println(constructorModifiers);
}

В результате увидим всю нужную для нас информацию:

Класс Employee:
Конструкторы :
[private ru.javarush.Employee(java.lang.String), public
ru.javarush.Employee(java.lang.String,java.lang.String,int), public ru.javarush.Employee()]
Модификаторы :
[private, public, public]

getExceptionTypes()

Этот метод позволяет получить массив исключений, которые может выбросить наш конструктор. Давай модифицируем один из наших конструкторов и напишем новый метод.

Тут мы немного изменили наш текущий приватный конструктор:


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

А тут у нас метод для получения типов исключений и добавления его в main:


static List<Class<?>> getConstructorExceptionTypes(Constructor<?> c) {
      return new ArrayList<>(Arrays.asList(c.getExceptionTypes()));
}


var constructorExceptionTypes = getConstructorExceptionTypes(constructors.get(0));
System.out.println("Типы исключений :");
System.out.println(constructorExceptionTypes);

Выше мы обратились к первому конструктору из нашего списка. Как получить определенный конструктор, мы обсудим чуть позже.

И посмотрим на вывод после добавления throws Exception:

Типы исключений :
[class java.lang.Exception]

И до добавления исключения:

Типы исключений :
[]

Все прекрасно, но как посмотреть, какие параметры нужны для наших конструкторов? Давай разберем и эту часть.

getParameters() & getParameterTypes() & getGenericParameterTypes()

Начнем мы опять с доработки нашего приватного конструктора. Теперь он будет выглядеть вот так:


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

И у нас появляются три дополнительных метода: getParameters для получения очередности параметров и их типов, getParameterTypes для получения типов параметров и getGenericParameterTypes для получения типов, обернутых в generics.


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

И в наш уже и не так маленький main добавляем еще немного информации:


var constructorParameterTypes = getConstructorParameterTypes(constructors.get(0));
var constructorParameters = getConstructorParameters(constructors.get(0));
var constructorParametersGenerics = getConstructorParametersGenerics(constructors.get(0));

System.out.println("Параметры конструкторов :");
System.out.println(constructorParameters);

System.out.println("Типы параметров :");
System.out.println(constructorParameterTypes);

System.out.println("Типы параметров конструкторов :");
System.out.println(constructorParametersGenerics);

Если мы взглянем на наш результат, то получим очень подробные данные о параметрах наших конструкторов:

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

Здесь отлично видно разницу каждого из методов. Мы видим, что можно отдельно получить данные о типах параметров, данные о классах обертках и о всем в целом. Супер! Мы закончили знакомство с классом Constructor и теперь можем вернуться к основной теме нашей статьи — к созданию объектов.

Создания объекта через Constructor.newInstance()

Второй способ создания объектов — вызов метода newInstance у конструктора. Давай взглянем на пример работы и посмотрим, как нам получить определенный конструктор.

Если перед тобой стоит задача получить один конструктор, тебе необходимо использовать метод getConstructor (не путай с getConstructors, который возвращает массив всех конструкторов). Метод getConstructor возвращает конструктор по умолчанию.


public static void main(String[] args) throws NoSuchMethodException {
    Class employeeClass = Employee.class;
    Constructor<?> employeeConstructor = employeeClass.getConstructor();
    System.out.println(employeeConstructor);
}
public ru.javarush.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("NeIvan", "NeIvanov", 10);
System.out.println(newInstance);

И в результате у нас есть объект, с которым мы можем дальше работать:

public ru.javarush.Employee(java.lang.String,java.lang.String,int)
Employee{name=’NeIvan’ surname=’NeIvanov’, age=10}

Все отлично работает! Теперь пробуем с приватным конструктором:


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

Employee newInstance2 = (Employee) declaredConstructor.newInstance("NeIvan", "NeIvanov", new ArrayList<>());
System.out.printf(newInstance2.toString());

И в результате мы получим ошибку о приватности нашего конструктора:

Java не смогла создать объект через этот конструктор, но на самом деле есть маленькая волшебная штука, которую мы укажем в методе main. Она установит доступность к нашему конструктору и позволит создавать объекты нашего класса:


declaredConstructor.setAccessible(true);

Результат создания объекта:

private ru.javarush.Employee(java.lang.String,java.lang.String,java.util.List)
Employee{name=’NeIvan’, surname=’NeIvanov’, age=-1}

Мы не устанавливаем возраст в нашем конструкторе, и он остается такой же, как и при инициализации.

Замечательно, пора подводить итоги!

Преимущества создания через Constructor.newInstance()

По названию оба метода выглядят одинаково, но между ними есть различия:

Class.newInstance() Constructor.newInstance()
Может вызывать только конструктор no-arg. Может вызывать любой конструктор независимо от количества параметров.
Требует, чтобы конструктор был виден. Также может вызывать приватные конструкторы при определенных обстоятельствах.
Выдает любое исключение (проверяемое или нет), которое задекларировано конструктором. Всегда обертывает выданное исключение с помощью InvocationTargetException.

По этим причинам Constructor.newInstance() предпочтительнее Class.newInstance(), и именно он используется различными фреймворками и API, такими как Spring, Guava, Zookeeper, Jackson, Servlet и т. д.