Java 高级特性の反射

571 篇文章 6 订阅
571 篇文章 3 订阅

反射存在的意义:

有的操作在硬编码的时候,无法实现,必须要在运行的时候获取到相关参数才能使用

反射可以实现的功能

根据指定的 class ,以及对应的实例 obj ,来获取 obj 上的所有属性,调用 obj 的所有方法,以及构造器

典型的场景

spring 上古时代的版本,2.x,那个时候主流就是 xml 配置,比如下面一段,我们配置了 User.class 的一个实例 user ,spring 框架使用 class 属性中的文本描述,以 Class.forName(className) (或其他方式,来加载 User ,然后根据 xml 里的配置生成一个 user 实例

<bean id="user" class="com.wb.bean.User">
</bean>
复制代码

工作场景

有时候我们需要编写通用的代码,不太合适直接对指定类直接进行硬编码,或者是对这一系列的类型都无法使用泛型来处理

比如,我需要生成一个测试类,需要给他的字段随机赋值

或者,我需要复制一个实例,如果硬编码类似于 b.setName(a.getName()) ,就无法做到通用性(而且代码又会显得很呆)

亦或者,我有 2 套数据模版,需要根据某种规则,从 classA 的实例映射为 ClassB 实例

代码演示

演示一下我们工作中反射的最最常见的使用

@ToString
// 本次演示用到的 bean
class User {
  String name;
  String job;
  User() {}
  private User(String name) {
    this.name = name;
  }
  private void sayHi(String prev) {
    System.out.printf("%s, I'm %s, %s is my job.", prev, name, job);
  }
}
// 使用 junit 进行测试
@Test
public void f2() throws Exception {
  // 获取到 class 的类型数据
  Class<?> clz = User.class;
  // Class<?> clz = Class.forName("com.wb.User"); // 如果我们的系统在运行之后从其他途径加载了 class 文件,我们可以使用根据文本描述的类路径来加载,比如,我们初学 jdbc 的时候就是这样加载 mysql 的 dirver
  // 根据参数列表来选择对应的构造器
  Constructor<?> oneParams = clz.getDeclaredConstructor(String.class);
  // 设置可以获取到私有构造器
  oneParams.setAccessible(true);
  // 通过构造器进行实例化
  Object obj = oneParams.newInstance("wangb");
  System.out.println(obj);  // 输出 User(name=wangb, job=null, dept=null)
  // 根据文本描述去获取 job 这个字段
  Field job = clz.getDeclaredField("job");
  // 设置可以访问私有属性
  job.setAccessible(true);
  // 进行赋值操作
  job.set(obj, "touch-fish");
  System.out.println(obj);  // 输出 User(name=wangb, job=touch-fish, dept=null)
  // 获取 clz 上的 sayHi(String) 方法
  Method sayHi = clz.getDeclaredMethod("sayHi", String.class);
  // 通用设置为获取私有方法
  sayHi.setAccessible(true);
  // 进行方法调用
  sayHi.invoke(obj, "morning"); // morning, I'm wangb, touch-fish is my job.
}
复制代码

通过上面一个测试用例,我们会有一种很强烈的感觉

我们是站在一个上帝视角,知道可以获取哪些属性,使用哪些 方法、构造器,而这些东西,在我们 coding 的时候,是不存在的!

细节讲解

我们使用的反射相关的类,都位于 java.lang.reflect

通过 clz.getxxx 去获取属性、方法、构造器时,有 2 种可选项,以获取属性为例

Field name = clz.getDeclaredField(name);
Field name2 = clz.getField(name);

前者额外执行一步操作 name.setAccessible(true); 即可访问私有的字段,然后可以对 name 进行读写;后者,只能获取到 public 的数据

获取全部构造器

Constructor<?>[] constructors = clz.getConstructors();
复制代码

获取全部方法

Method[] methods = clz.getDeclaredMethods();
复制代码

获取全部字段

Field[] fields = clz.getDeclaredFields();
复制代码

当我们需要获取到全部的属性时,一般会使用 foreach 循环进行遍历

如下示意:

for (Field f : fields) {
  // do somethings
}
复制代码

最后

反射虽然说是高级特性,但是这 api 也没多少,重要是我们理解了反射能做什么事情之后,发现这并没什么难的,无外乎根据需要去绕两圈实现我们想要的东西

当然,需要说明的是,毕竟不是直接调用, 反射会存在性能问题,如果需要经常根据反射去调用,最好缓存相关的 Fields, Methods ,避免重复获取造成性能浪费

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值