使用 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() { }
添加构造函数后,输出如下:
伟大的!我们弄清楚了这种方法是如何工作的。现在让我们来看看引擎盖下。打开文档,我们看到我们的方法已经被弃用了:

它还可以抛出InstantiationException和IllegalAccessException。因此,文档建议我们使用另一种创建对象的方法,即Constructor.newInstance()。下面详细分析一下Constructor类是如何工作的。
getConstructors 和 getDeclaredConstructors 方法
要使用Constructor类,我们首先需要获取一个实例。为此,我们有两种方法:getConstructors和getDeclaredConstructors。
第一个返回公共构造函数的数组,第二个返回所有类构造函数的数组。
让我们给我们的类一些隐私,或者更确切地说,让我们创建一些私有构造函数来帮助演示这些方法是如何工作的。
让我们添加一些私有构造函数:
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);
}
}
}
我们得到这个结果:
公共 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()
让我们将数组包装在一个列表中,以便于使用。我们还将编写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);
上面,我们访问了列表中的第一个构造函数。稍后我们将讨论如何获得特定的构造函数。
并查看我们添加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);
}
而如果我们想得到具体的构造函数,就需要将构造函数的参数类型传递给这个方法。
不要忘记我们只能使用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);
结果是一个我们可以使用的对象:
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);
创建对象的结果
Employee{name='Rob', surname='Stark', age=-1}
我们没有在构造函数中设置年龄,因此它与初始化时保持不变。
精彩,总结一下!
使用 Constructor.newInstance() 创建对象的好处
这两种方法具有相同的名称,但它们之间存在差异:
类.newInstance() | 构造函数.newInstance() |
---|---|
只能调用无参数构造函数。 | 无论参数的数量如何,都可以调用任何构造函数。 |
要求构造函数可见。 | 在某些情况下也可以调用私有构造函数。 |
抛出构造函数声明的任何异常(已检查或未检查)。 | 始终使用InvocationTargetException包装抛出的异常。 |
由于这些原因,Constructor.newInstance()优于Class.newInstance(),并且是各种框架和 API 使用的方法,例如 Spring、Guava、Zookeeper、Jackson、Servlet 等。
GO TO FULL VERSION