【注解和反射】通过反射动态创建对象、调用普通方法、操作属性

继上一篇博客【注解和反射】获取类运行时结构-CSDN博客

目录

八、通过反射动态创建对象

测试:通过反射动态创建对象

思考:难道没有无参的构造器就不能创建对象了吗?只要在操作的时候明确的调用类中的构造器并将参数传递进去之后,才可以实例化操作。

测试

测试:通过反射调用普通方法、通过反射操作属性


八、通过反射动态创建对象

在Java中,通过反射可以动态地创建对象,而不需要在编译时知道要创建的对象的具体类。以下是使用Java反射API创建对象的基本步骤:

  1. 获取Class对象 : 首先,需要获取代表想要创建对象的类的Class对象。这可以通过几种方式之一来完成:

    • 使用.class语法,如果类名已知。
    • 使用Class.forName()方法,通过类的全限定名(包括包名)获取Class对象。这在类名在运行时才知道的情况下很有用。
  2. 检查访问权限 : 如果该类或其构造函数不是可访问的(即不是public),则需要通过调用setAccessible(true)方法来临时允许访问。请注意,这种做法可能会违反Java的安全性原则,应谨慎使用。

  3. 获取Constructor对象 : 接下来,需要获取Constructor对象,该对象表示类的构造函数。这可以通过调用Class对象的getDeclaredConstructor()方法来完成,该方法可以接收构造函数的参数类型作为参数。如果想要获取的是公共构造函数,则可以使用getConstructor()方法。

  4. 创建实例 : 一旦获得了Constructor对象,就可以调用它的newInstance()方法来创建类的新实例。如果构造函数需要参数,那么这些参数需要传递给newInstance()方法。

测试:通过反射动态创建对象

创建一个User类,

java 复制代码
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 +
      '}';
  }
}
java 复制代码
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类不存在,或者不可访问(例如因为它是私有的或者在不同的包中并且没有公共的构造函数,那么程序将抛出异常 。这里添加了NoSuchMethodExceptionInvocationTargetExceptionthrows子句中,因为getDeclaredConstructor()newInstance()方法可能会抛出这些异常。具体来说,

  • ClassNotFoundException将在类不存在时抛出
  • InstantiationException将在类是一个接口或抽象类时抛出
  • IllegalAccessException将在没有适当的访问权限时抛出

由于c1被声明为Class类型而不是Class<User>类型,编译器会发出未经检查的转换警告,因为将c1.newInstance()的结果转换为User类型时没有进行类型检查。为了避免这个警告,可以将c1的声明更改为Class<?> c1Class<User> c1(如果确实知道要加载的类类型)。

另外,从Java 9开始,Class.newInstance()方法已被弃用,因为它无法正确地与Java的新模块化系统一起工作。建议使用Class.getDeclaredConstructor().newInstance()来代替,这样可以更明确地指定要调用的构造函数,并且与Java的模块化系统兼容。

思考:难道没有无参的构造器就不能创建对象了吗?只要在操作的时候明确的调用类中的构造器并将参数传递进去之后,才可以实例化操作。

如果目标类(例如com.itheima.sjms.User)没有无参的构造器,而你又想通过反射来创建其实例,

  1. 可以使用Class对象的**getDeclaredConstructor方法**来指定参数类型并获取相应的构造器。
  2. 向构造器的形参中传递一个对象数组进去里面包含了构造器中所需的各个参数
  3. 通过Constructor实例化对象:可以调用newInstance方法(在Java 8及以前)或者从Java 9开始推荐的Constructor.newInstance的替代方法,invoke,来创建对象实例。

测试

java 复制代码
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 方法。

java 复制代码
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)通过反射调用普通方法步骤是:

  1. 首先使用Class.forName加载User类,并获取其Class对象,用Class对象的newInstance方法创建User类的一个新实例。
  2. 获取User类中声明的setName方法,该方法接受一个String参数
  3. 使用Method对象的invoke方法来调用user3对象的setName方法,并传入"user3"作为参数,
  4. 打印user3对象(这将调用User类的toString方法,如果User类没有重写toString,将打印对象的类名和哈希码)

(2)通过反射操作属性步骤是:

  1. 首先使用Class.forName加载User类,并获取其Class对象,用Class对象的newInstance方法创建User类的一个新实例。
  2. 获取User类中声明的name字段(无论它是公有还是私有)
  3. 设置name字段为可访问,以便能够修改私有字段的值
  4. 使用Field对象的set方法来设置user4对象的name字段的值为"user4"
  5. 调用user4对象的getName方法来获取name字段的值,并打印它

在Java反射API中,Method类的invoke方法允许你动态地调用一个方法。这个方法的签名如下:

java 复制代码
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类的一个方法,它被FieldMethodConstructor类继承。这个方法用于设置反射对象(字段、方法或构造函数)的可访问性,允许你访问和修改私有成员,或者调用私有方法。

setAccessible()方法接受一个布尔值参数:

  • 如果参数为true,则指示反射的对象在使用时应该取消Java语言访问检查。这允许你访问任何字段、调用任何方法或实例化任何类,无论它们是否是私有的。
  • 如果参数为false,则反射的对象将进行正常的Java语言访问检查。

使用setAccessible(true)可以带来性能提升,因为它避免了Java安全管理器的安全检查,但这也会带来安全风险,因为它破坏了封装性并可能违反安全策略。因此,在使用setAccessible(true)时应该谨慎,并确保不会引入安全漏洞。

相关推荐
远望清一色几秒前
基于MATLAB的实现垃圾分类Matlab源码
开发语言·matlab
confiself10 分钟前
大模型系列——LLAMA-O1 复刻代码解读
java·开发语言
Wlq041515 分钟前
J2EE平台
java·java-ee
XiaoLeisj21 分钟前
【JavaEE初阶 — 多线程】Thread类的方法&线程生命周期
java·开发语言·java-ee
杜杜的man25 分钟前
【go从零单排】go中的结构体struct和method
开发语言·后端·golang
幼儿园老大*26 分钟前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
半桶水专家26 分钟前
go语言中package详解
开发语言·golang·xcode
llllinuuu27 分钟前
Go语言结构体、方法与接口
开发语言·后端·golang
cookies_s_s28 分钟前
Golang--协程和管道
开发语言·后端·golang
王大锤439130 分钟前
golang通用后台管理系统07(后台与若依前端对接)
开发语言·前端·golang