使用 Class.newInstance() 创建对象的示例

想象一下,您被分配使用反射创建一个对象。我们开始吧?

我们将从编写要实例化的类的代码开始:

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

这将是我们的类——有几个字段、一个带参数的构造函数、getter 和 setter、一个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() { }

添加构造函数后,输出如下:

年龄是 1

伟大的!我们弄清楚了这种方法是如何工作的。现在让我们来看看引擎盖下。打开文档,我们看到我们的方法已经被弃用了

它还可以抛出InstantiationExceptionIllegalAccessException。因此,文档建议我们使用另一种创建对象的方法,即Constructor.newInstance()。下面详细分析一下Constructor类是如何工作的。

getConstructors 和 getDeclaredConstructors 方法

要使用Constructor类,我们首先需要获取一个实例。为此,我们有两种方法:getConstructorsgetDeclaredConstructors

第一个返回公共构造函数的数组,第二个返回所有类构造函数的数组。

让我们给我们的类一些隐私,或者更确切地说,让我们创建一些私有构造函数来帮助演示这些方法是如何工作的。

让我们添加一些私有构造函数:

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

查看代码,请注意其中一个构造函数是私有的:

让我们测试一下我们的方法:

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

好的,这就是我们访问Constructor对象的方式。现在我们可以谈谈它能做什么。

java.lang.reflect.Constructor 类及其最重要的方法

让我们来看看最重要的方法及其工作原理:

方法 描述
获取名称() 以字符串形式返回此构造函数的名称。
getModifiers() 返回编码为数字的 Java 访问修饰符。
getExceptionTypes() 方法 返回一个 Class 对象数组,这些对象表示构造函数声明的异常类型。
获取参数() 返回表示所有参数的参数对象数组。如果构造函数没有参数,则返回长度为 0 的数组。
获取参数类型() 返回一个 Class 对象数组,这些对象按声明顺序表示形式参数类型。
getGenericParameterTypes() 返回一个 Type 对象数组,这些对象按声明顺序表示形式参数类型。

getName() 和 getModifiers()

让我们将数组包装在一个列表中,以便于使用。我们还将编写getNamegetModifiers方法:

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

上面,我们访问了列表中的第一个构造函数。稍后我们将讨论如何获得特定的构造函数。

并查看我们添加throws Exception后的输出:

异常类型:
[class java.lang.Exception]

在添加异常之前:

异常类型:
[]

一切都很棒,但是我们如何查看我们的构造函数需要哪些参数呢?让我们也弄清楚这一点。

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

我们向已经不小的main方法添加更多信息:

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]
参数类型:
[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);
}
公共 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 无法使用此构造函数创建对象,但实际上我们可以在main方法中做一些神奇的事情。我们可以访问构造函数的访问级别,从而可以创建我们类的对象:

declaredConstructor.setAccessible(true);

创建对象的结果

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

我们没有在构造函数中设置年龄,因此它与初始化时保持不变。

精彩,总结一下!

使用 Constructor.newInstance() 创建对象的好处

这两种方法具有相同的名称,但它们之间存在差异:

类.newInstance() 构造函数.newInstance()
只能调用无参数构造函数。 无论参数的数量如何,都可以调用任何构造函数。
要求构造函数可见。 在某些情况下也可以调用私有构造函数。
抛出构造函数声明的任何异常(已检查或未检查)。 始终使用InvocationTargetException包装抛出的异常。

由于这些原因,Constructor.newInstance()优于Class.newInstance(),并且是各种框架和 API 使用的方法,例如 Spring、Guava、Zookeeper、Jackson、Servlet 等。