继上一篇博客【注解和反射】获取类运行时结构-CSDN博客
目录
八、通过反射动态创建对象
测试:通过反射动态创建对象
思考:难道没有无参的构造器就不能创建对象了吗?只要在操作的时候明确的调用类中的构造器并将参数传递进去之后,才可以实例化操作。
测试
测试:通过反射调用普通方法、通过反射操作属性
八、通过反射动态创建对象
在Java中,通过反射可以动态地创建对象,而不需要在编译时知道要创建的对象的具体类。以下是使用Java反射API创建对象的基本步骤:
-
获取Class对象: 首先,需要获取代表想要创建对象的类的
Class
对象。这可以通过几种方式之一来完成:- 使用
.class
语法,如果类名已知。 - 使用
Class.forName()
方法,通过类的全限定名(包括包名)获取Class
对象。这在类名在运行时才知道的情况下很有用。
- 使用
-
检查访问权限: 如果该类或其构造函数不是可访问的(即不是
public
),则需要通过调用setAccessible(true)
方法来临时允许访问。请注意,这种做法可能会违反Java的安全性原则,应谨慎使用。 -
获取Constructor对象: 接下来,需要获取
Constructor
对象,该对象表示类的构造函数。这可以通过调用Class
对象的getDeclaredConstructor()
方法来完成,该方法可以接收构造函数的参数类型作为参数。如果想要获取的是公共构造函数,则可以使用getConstructor()
方法。 -
创建实例: 一旦获得了
Constructor
对象,就可以调用它的newInstance()
方法来创建类的新实例。如果构造函数需要参数,那么这些参数需要传递给newInstance()
方法。
测试:通过反射动态创建对象
创建一个User类,
class User{
private String name;
private int id;
private int age;
public User(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
public User() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
'}';
}
}
public class Test06 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class c1 = Class.forName("com.itheima.sjms.User");
User user = (User) c1.newInstance();
System.out.println(user);
}
}
从结果中我们可以看到,
(1)com.itheima.sjms.User
类存在,并且该类有一个可访问的无参构造函数,那么程序将成功运行。c1.newInstance()
方法会调用com.itheima.sjms.User
类的无参构造函数来创建一个新的User
对象实例。
System.out.println(user);
语句将打印新创建的User
对象。默认情况下,这将输出User
对象的类名和哈希码的无意义表示(例如com.itheima.sjms.User@15db9742
),除非User
类重写了toString()
方法以提供更有意义的输出。
(2)如果com.itheima.sjms.User
类不存在,或者不可访问(例如因为它是私有的或者在不同的包中并且没有公共的构造函数),那么程序将抛出异常。这里添加了NoSuchMethodException
和InvocationTargetException
到throws
子句中,因为getDeclaredConstructor()
和newInstance()
方法可能会抛出这些异常。具体来说,
ClassNotFoundException
将在类不存在时抛出InstantiationException
将在类是一个接口或抽象类时抛出IllegalAccessException
将在没有适当的访问权限时抛出
由于c1
被声明为Class
类型而不是Class<User>
类型,编译器会发出未经检查的转换警告,因为将c1.newInstance()
的结果转换为User
类型时没有进行类型检查。为了避免这个警告,可以将c1
的声明更改为Class<?> c1
或Class<User> c1
(如果确实知道要加载的类类型)。
另外,从Java 9开始,Class.newInstance()
方法已被弃用,因为它无法正确地与Java的新模块化系统一起工作。建议使用Class.getDeclaredConstructor().newInstance()
来代替,这样可以更明确地指定要调用的构造函数,并且与Java的模块化系统兼容。
思考:难道没有无参的构造器就不能创建对象了吗?只要在操作的时候明确的调用类中的构造器并将参数传递进去之后,才可以实例化操作。
如果目标类(例如com.itheima.sjms.User
)没有无参的构造器,而你又想通过反射来创建其实例,
- 可以使用
Class
对象的getDeclaredConstructor
方法来指定参数类型并获取相应的构造器。 - 向构造器的形参中传递一个对象数组进去里面包含了构造器中所需的各个参数
- 通过Constructor实例化对象:可以调用
newInstance
方法(在Java 8及以前)或者从Java 9开始推荐的Constructor.newInstance
的替代方法,invoke
,来创建对象实例。
测试
public class Test06 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class c1 = Class.forName("com.itheima.sjms.User");
Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
User user2 = (User)declaredConstructor.newInstance("user", 1, 10);
System.out.println(user2);
}
}
在Java 9及以上版本,Constructor.newInstance
已被弃用,因为它不支持varargs
(可变参数)且可能与新的模块化系统不完全兼容。取而代之的是使用Constructor.newInstance
的重载版本,该版本接受一个Class<?>[]
参数类型数组和一个Object[]
参数值数组,或者直接使用invoke
方法。
测试:通过反射调用普通方法、通过反射操作属性
试图通过反射来创建 com.itheima.sjms.User
类的实例,并调用其 setName
方法。
public class Test06 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Class c1 = Class.forName("com.itheima.sjms.User");
User user3 = (User)c1.newInstance();
Method setName = c1.getDeclaredMethod("setName", String.class);
setName.invoke(user3, "user3");
System.out.println(user3);
System.out.println("------------");
User user4 = (User)c1.newInstance();
Field name = c1.getDeclaredField("name");
name.setAccessible(true);
name.set(user4, "user4");
System.out.println(user4.getName());
}
}
(1)通过反射调用普通方法步骤是:
- 首先使用Class.forName加载User类,并获取其Class对象,用Class对象的newInstance方法创建User类的一个新实例。
- 获取User类中声明的setName方法,该方法接受一个String参数
- 使用Method对象的invoke方法来调用user3对象的setName方法,并传入"user3"作为参数,
- 打印user3对象(这将调用User类的toString方法,如果User类没有重写toString,将打印对象的类名和哈希码)
(2)通过反射操作属性步骤是:
- 首先使用Class.forName加载User类,并获取其Class对象,用Class对象的newInstance方法创建User类的一个新实例。
- 获取User类中声明的name字段(无论它是公有还是私有)
- 设置name字段为可访问,以便能够修改私有字段的值
- 使用Field对象的set方法来设置user4对象的name字段的值为"user4"
调用user4对象的getName方法来获取name字段的值,并打印它
在Java反射API中,
Method
类的invoke
方法允许你动态地调用一个方法。这个方法的签名如下:public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
参数解释:
obj
:这是方法调用的目标对象。如果这个方法是静态的,那么这个参数可以为null
;如果是实例方法,那么这个参数就是包含此方法的实例对象。args
:这是一个可变参数,代表调用目标方法时传入的参数列表。参数列表的数据类型和顺序必须与方法签名中定义的完全匹配。如果方法没有参数,那么这个参数可以省略。
invoke
方法的返回值是被调用方法的返回值。如果被调用方法没有返回值(即void
方法),则invoke
返回null
。异常:
IllegalAccessException
:如果这个方法是不可访问的,比如一个私有方法且没有被设置为可访问(通过setAccessible(true)
)。IllegalArgumentException
:如果传入的参数和方法签名不匹配。InvocationTargetException
:如果目标方法抛出了异常,那么这个异常会被包装在InvocationTargetException
中。
在Java反射API中,
setAccessible()
方法是AccessibleObject
类的一个方法,它被Field
、Method
和Constructor
类继承。这个方法用于设置反射对象(字段、方法或构造函数)的可访问性,允许你访问和修改私有成员,或者调用私有方法。
setAccessible()
方法接受一个布尔值参数:
- 如果参数为
true
,则指示反射的对象在使用时应该取消Java语言访问检查。这允许你访问任何字段、调用任何方法或实例化任何类,无论它们是否是私有的。- 如果参数为
false
,则反射的对象将进行正常的Java语言访问检查。使用
setAccessible(true)
可以带来性能提升,因为它避免了Java安全管理器的安全检查,但这也会带来安全风险,因为它破坏了封装性并可能违反安全策略。因此,在使用setAccessible(true)
时应该谨慎,并确保不会引入安全漏洞。