Java反射机制学习笔记

1. 反射机制概述

作用

通过反射机制可以操作字节码文件。

相关类

位于 java.lang.reflect.* 包中,重要类包括:

  • java.lang.Class ------ 代表整个字节码,代表整个类
  • java.lang.reflect.Method ------ 代表字节码中的方法,代表类中的方法
  • java.lang.reflect.Constructor ------ 代表字节码中的构造方法,代表类中的构造方法
  • java.lang.reflect.Field ------ 代表字节码中的属性,代表类中的成员变量

要操作一个类的字节码,就要先获取这个类的字节码(Class 对象)。

2. 获取 Class 对象的三种方式

2.1 第一种:Class.forName()

  • 静态方法
  • 参数是一个完整的类名(必须带包名,java.lang 包也不能省略)
  • 示例:
java 复制代码
Class c1 = Class.forName("java.lang.String");

该方法会导致类加载。如果只希望执行一个类的静态代码块而不执行其他代码,可以使用这个方法(静态代码块在类加载时执行,且只执行一次)。

2.2 第二种:getClass()

Java 中任何对象都有这个方法,通过对象可以获取对应的类的字节码。

java 复制代码
String s = "111";
Class c2 = s.getClass();   // 得到 String.class
boolean b = (c1 == c2);    // true

2.3 第三种:.class 属性

Java 中任一类型都有 .class 属性,包括基本数据类型。

java 复制代码
Class c3 = String.class;
boolean b2 = (c1 == c3);   // true

3. 通过反射实例化对象

Class 类提供了 newInstance() 方法,会调用该类的无参构造方法。

java 复制代码
Object o = c3.newInstance();   // 调用 String 类的无参构造

通过反射机制创建对象的优点在于灵活性:可以在不改变源码的情况下实现不同对象的实例化。

4. 文件路径与资源加载

直接使用绝对路径会导致移植性差。可以使用一种与代码位置无关的通用路径,前提是文件必须放在类路径下(src 目录下就是类路径,src 是类的根路径)。

4.1 获取文件的绝对路径

java 复制代码
String path = Thread.currentThread()
                     .getContextClassLoader()
                     .getResource("从src之后的文件路径")
                     .getPath();
  • getContextClassLoader():线程对象的方法,获取当前线程的类加载器对象。
  • getResource():类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源。

4.2 以流的形式返回

java 复制代码
InputStream reader = Thread.currentThread()
                            .getContextClassLoader()
                            .getResourceAsStream("从src之后的文件路径");

4.3 资源绑定器(ResourceBundle)

java.util 包提供了资源绑定器,便于获取属性配置文件中的内容。使用该方式时,属性配置文件必须放在类路径下。资源绑定器只能绑定 .properties 文件,且写路径时不写扩展名。

java 复制代码
ResourceBundle bundle = ResourceBundle.getBundle("文件路径");
String s = bundle.getString("key");   // 获取 value

5. 类加载器

5.1 什么是类加载器

专门负责加载类的命令/工具,即 ClassLoader

5.2 JDK 自带的三个类加载器

  • 启动类加载器(Bootstrap ClassLoader)
  • 扩展类加载器(Extension ClassLoader)
  • 应用类加载器(Application ClassLoader)

5.3 加载过程与双亲委派机制

假设有以下代码:

java 复制代码
String a = "111";

代码开始执行前,会将所需类全部加载到 JVM 中。见到以上代码,类加载器会找到 String.class 文件,加载过程如下:

  1. 首先通过"启动类加载器"加载。
    启动类加载器专门加载 jre/lib/rt.jarrt.jar 是 JDK 最核心的类库。
  2. 如果启动类加载器加载不到,则通过"扩展类加载器"加载。
    扩展类加载器专门加载 jre/lib/ext/*.jar
  3. 如果扩展类加载器也加载不到,最后通过"应用类加载器"加载。
    应用类加载器专门加载 classpath 中的类。

这种"启动类加载器 → 扩展类加载器 → 应用类加载器"的机制称为双亲委派机制,是一种安全机制。

6. 反射操作 Field

6.1 获取 Field

java 复制代码
Class c = Class.forName("完整类名");
// 获取类中所有 public 修饰的 Field
Field[] fields = c.getFields();
// 获取所有的 Field
Field[] fs = c.getDeclaredFields();
for (Field field : fs) {
    int i = field.getModifiers();           // 修饰符代号
    String m = Modifier.toString(i);        // 转为字符串
    String ft = field.getType().getName();  // 属性类型
    String fn = field.getName();            // 属性名称
}

6.2 反编译 Field(通过反射将类的属性源码还原出来)

java 复制代码
Class c = Class.forName("完整类名");
Field[] fields = c.getFields();
StringBuilder s = new StringBuilder();
s.append(Modifier.toString(c.getModifiers()) + " class " + c.getSimpleName() + " {\n");
Field[] fs = c.getDeclaredFields();
for (Field field : fs) {
    s.append("\t");
    int i = field.getModifiers();
    String m = Modifier.toString(i);
    s.append(m);
    s.append(" ");
    String ft = field.getType().getSimpleName();
    s.append(ft);
    s.append(" ");
    String fn = field.getName();
    s.append(fn);
    s.append(";\n");
}
s.append("}");
System.out.println(s);

6.3 通过反射访问对象的属性(重点)

给属性赋值 / 获取属性的值

java 复制代码
Class c = Class.forName("完整类名");
Object o = c.newInstance();
// 获取属性
Field f = c.getDeclaredField("属性名称");
// 若要访问私有属性,必须先打破封装
f.setAccessible(true);
// 赋值
f.set(o, 要赋的值);
// 获取属性的值
f.get(o);

7. 可变长度参数

语法:类型...,例如 int... a

  • 参数个数可以是 0 到 N 个
  • 类型不能改变
  • 可变长度参数在形参列表中只能放在最后面,且只能有一个
  • 可变长度参数可以当作数组使用,也可以直接传入数组

8. 反射操作 Method

8.1 获取 Method

java 复制代码
Class c = Class.forName("完整类名");
Method[] ms = c.getDeclaredMethods();
for (Method m : ms) {
    String n = m.getName();                                  // 方法名
    String t = m.getReturnType().getSimpleName();            // 返回值类型
    String md = Modifier.toString(m.getModifiers());         // 修饰符列表
    Class[] pts = m.getParameterTypes();                     // 形参类型
    for (Class pt : pts) {
        String pn = pt.getSimpleName();
    }
}

8.2 通过反射机制调用方法(重点)

java 复制代码
Class c = Class.forName("完整类名");
Object o = c.newInstance();
Method m = c.getDeclaredMethod("方法名", 形参类型.class, ...);
Object retValue = m.invoke(o, 实参, ...);   // 获取返回值

9. 反射操作 Constructor

9.1 获取 Constructor

java 复制代码
Class c = Class.forName("完整类名");
Constructor[] cs = c.getDeclaredConstructors();
// 反编译方式同之前 Field 的反编译

9.2 使用反射机制创建对象

java 复制代码
Class c = Class.forName("完整类名");
// 调用无参构造
Object o = c.newInstance();
// 获取有参构造方法
Constructor con = c.getDeclaredConstructor(形参类型.class, ...);
Object o2 = con.newInstance(实参, ...);

10. 获取父类和父接口(重点)

java 复制代码
Class c = Class.forName("完整类名");
Class sc = c.getSuperclass();
Class[] inf = c.getInterfaces();   // 一个类可以实现多个接口,所以用数组
相关推荐
Loving_enjoy1 小时前
DFT可测试性设计规则与检查:确保芯片可测试性的关键步骤
学习
jason成都1 小时前
GNSS-QC 数据清洗功能全面增强|滑坡监测实测验证,适配 rtklib_java 高精度解算链路
java·gnss
前端工作日常1 小时前
我学习到的Java概念
java·后端
卓怡学长1 小时前
w263基于springboot + vue 健康饮食管理系统
java·数据库·vue.js·spring boot·spring·intellij-idea
九皇叔叔2 小时前
RHEL9.8 配置本地镜像仓库
android·java·缓存
范什么特西2 小时前
苍穹外卖体会
java
Nontee2 小时前
数据类型与包装类 — 一个新手学Java的数据类型笔记
java·笔记·python
ysa0510302 小时前
【板子】树上启发式合并
数据结构·c++·笔记·算法
音符犹如代码2 小时前
Java动态线程池:避坑原生线程池,吃透Dynamic-TP,手写一个Demo
java·开发语言