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 文件,加载过程如下:
- 首先通过"启动类加载器"加载。
启动类加载器专门加载jre/lib/rt.jar,rt.jar是 JDK 最核心的类库。 - 如果启动类加载器加载不到,则通过"扩展类加载器"加载。
扩展类加载器专门加载jre/lib/ext/*.jar。 - 如果扩展类加载器也加载不到,最后通过"应用类加载器"加载。
应用类加载器专门加载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(); // 一个类可以实现多个接口,所以用数组