反射(reflection)
反射(reflection)
// 已知某个类得实例,调用该实例得 getClass() 方法获取 Class 对象 【就是 运行类型】
Car car = new Car();
Class<? extends Car> aClass3 = car.getClass();
System.out.println(aClass3);
// 对于某个类的 Class 对象,在内存中只有一份,因为类只加载一次
Class<?> aClass = Class.forName("com.hspedu.Cat");
Class<?> aClass1 = Class.forName("com.hspedu.Cat");
Class<?> aClass2 = Class.forName("com.hspedu.Dog");
System.out.println(aClass.hashCode()); // 666641942
System.out.println(aClass1.hashCode()); // 666641942
System.out.println(aClass2.hashCode()); // 960604060
反射是框架的灵魂,是框架的底层基石,没有反射也就没什么框架
一个需求引入反射
- 根据配置文件 re.properties 指定信息,创建对象并调用方法
classFullPath=com.hspedu.Cat
method=hi
- 这样的需求在学习框架时特别多,即通过外部文件配置,在不修改源码情况下,来控制程序,也符合设计模式的 OCP原则(开闭原则:不修改源码,扩展功能)
传统的方法解决不了
public class ReflectionQuestion {
public static void main(String[] args) throws IOException {
// 根据配置文件 re.properties 指定信息,创建对象并调用方法 hi
// 传统方式
// 1. new 对象
// 2. 调用方法
// 我们尝试做一下 ---> 明白反射的价值
// 1. 使用 Properties 类,可以读写配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src/re.properties"));
String classFullPath = properties.get("classFullPath").toString(); // 转成字符串
String method = properties.get("method").toString();
System.out.println("classFullPath= " + classFullPath);
System.out.println("method= " + method);
// 2. 创建对象,传统方法,行不通。 ===》 反射机制
new classFullPath().var // new classFullPath() 【是String】。不是类的全路径
}
}
使用反射机制解决
// 3. 使用反射机制解决
// (1)加载类,返回 Class 类型的对象 [重点]
Class<?> aClass = Class.forName(classFullPath);
// (2)通过 aClass 得到你加载的类 com.hspedu.Cat 的对象实例
Object o = aClass.newInstance();
System.out.println("o的运行类型= " + o.getClass()); // 得到运行类型
// (3)通过 aClass 得到你加载的类 com.hspedu.Cat 的 methodName"hi" 的方法对象
// 即:在反射中,可以把方法视为对象(万物皆对象)
Method method1 = aClass.getMethod(methodName);
// (4)通过 method1 调用方法:即通过方法对象来实现调用方法
System.out.println("=================");
method1.invoke(o); // 传统方法 对象.方法(); 反射机制:方法对象.invoke(对象)
牛逼所在:设计模式的 OCP原则(开闭原则:不修改源码,扩展功能)
假如现在还有一个 cry () 方法 【传统方式,只能修改源码 : new Cat().hi() ---> new Cat().cry()】
public class Cat {
private String name = "招财猫";
public void hi(){ // 常用方法
System.out.println("hi" + name);
}
public void cry() { // 常用方法
System.out.println(name + "喵喵叫...");
}
}
现在只需要修改配置文件 re.properties 即可
classFullPath=com.hspedu.Cat
method=cry
1. 反射机制
- 反射机制允许程序在执行期借助于 Reflection API 取得任何类的内部信息(比如:成员变量,构造器,成员方法等等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到
- 加载完类之后,在堆中就产生了一个 Class 类型的对象(一个类只有一个Class 对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个Class对象就像是一面镜子,透过这个镜子看到类的结构,所以,形象称之为:反射
Person对象 p ---> 类型 Person 类
Class 对象 cls ---> 类型 Class 类
//////////////////////////////
人 ---> 张三
人 ---> 韩顺平
人 ---> 名字
Java 反射机制原理图
反射机制可以完成
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时得到任意一个类所具有的成员变量和方法
- 在运行时调用到任意一个对象的成员变量和方法
- 生成动态代理
反射相关的主要类
- java.lang.Class:代表一个类,Class 对象(class字节码文件)表示某个类加载后在堆中的对象
- java.lang.reflect.Method:代表类的方法,Method对象 表示某个类的方法
- java.lang.reflect.Field:代表类的成员变量,Filed对象表示某个类的成员变量
- java.lang.reflcet.Constructor:代表类的构造方法,Constructor对象表示构造器
这些类在 java.lang.reflection
public class Cat {
private String name = "招财猫";
public int age = 10;
public void hi(){ // 常用方法
System.out.println("hi" + name);
}
public Cat() {
}
public Cat(String name) {
this.name = name;
}
public void cry() { // 常用方法
System.out.println(name + "喵喵叫...");
}
}
public class Reflection01 {
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
// 1. 使用 Properties 类,可以读写配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src/re.properties"));
String classFullPath = properties.get("classFullPath").toString(); // 转成字符串
// 2. 使用反射机制解决
// (1)加载类,返回 Class 类型的对象
Class<?> aClass = Class.forName(classFullPath);
// (2)通过 aClass 得到你加载的类 com.hspedu.Cat 的对象实例
Object o = aClass.newInstance();
System.out.println("o的运行类型= " + o.getClass()); // 得到运行类型
// (3)java.lang.reflect.Field:代表类的成员变量,Field对象表示某个类的成员白能量
// 得到name 字段
// getField 不能得到私有的属性
Field age = aClass.getField("age");
System.out.println(age.get(o)); // 传统写法 对象.成员变量,反射:成员变量对象.get(对象)
// 获得构造器
Constructor<?> constructor = aClass.getConstructor(); // () 中可以指定构造器参数类型,不写则返回无参构造器
System.out.println(constructor);
Constructor<?> constructor1 = aClass.getConstructor(String.class); // 传入一个形参类型的 class 对象
System.out.println(constructor1);
}
}
反射优点和缺点
- 优点:可以动态地创建和使用对象(也是框架的核心),使用灵活,没有反射机制,框架技术就失去底层支撑
- 缺点:使用反射基本是解释执行,对执行速度有影响
测试反射调用性能
// IO是最耗时的,为了测试,将sout 注销
public void hi(){ // 常用方法
// System.out.println("hi" + name);
}
public class Reflection02 {
public static void main(String[] args) {
}
// 传统方法调用 hi ---> 4ms
@Test
public void m1(){
Cat cat = new Cat();
long start = System.currentTimeMillis();
for (int i = 0; i < 9000000; i++) {
cat.hi();
}
long end = System.currentTimeMillis();
System.out.println("传统执行时间:" + (end - start));
}
// 反射机制调用 hi ---> 49ms
@Test
public void m2() throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
// 1. 使用 properties 类,可以读写配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src/re.properties"));
String classFullPath = properties.get("classFullPath").toString();
// 2. 使用反射机制
Class<?> aClass = Class.forName(classFullPath);
Object o = aClass.newInstance(); // 创建对象
Method hi = aClass.getMethod("hi"); // 得到方法对象
System.out.println();
long start = System.currentTimeMillis();
for (int i = 0; i < 9000000; i++) {
hi.invoke(o); // 传统方法 对象.方法(),反射机制 方法对象.invoke(对象)
}
long end = System.currentTimeMillis();
System.out.println("反射机制执行时间:" + (end - start));
}
}
反射调用优化 --- 关闭访问检查
- Method 和 Field、Constructo对象都有 setAccessible() 方法
- setAccessible作用是启动和禁用访问安全检查的开关
- 参数为:
- true:反射的对象在使用时候取消访问检查,提高反射效率
- false:反射的对象执行访问检查
举例子:
java.lang.reflect Field
Field的父类 AccessibleOjbect 包含 setAccessible(boolean) 方法
同理 Method 和 Constructor 同样继承了 AccessibleOjbect 这个父类
// 反射调用优化 ---> 关闭访问检测
Class<?> aClass = Class.forName(classFullPath);
Object o = aClass.newInstance();
Method hi = aClass.getMethod("hi");
hi.setAccessible(true); // 在反射调用方法时,取消访问检查
System.out.println();
long start = System.currentTimeMillis();
for (int i = 0; i < 9000000; i++) {
hi.invoke(o);
}
long end = System.currentTimeMillis();
System.out.println("反射机制执行时间:" + (end - start));
2. Class类
- Class 也是类,因此也继承 Object 类
- Class 类对象不是 new 出来的,而是系统创建的
// (1)传统方式
public class cc {
public static void main(String[] args) {
Cat cat = new Cat(); // Debug
}
}
public Class<?> loadClass(String var1) throws ClassNotFoundException {
return this.loadClass(var1, false);
}
// (2)反射方式,刚才老师没有 debug 到 ClassLoader 类的 loadClass,原因是我没有注销掉 Cat cat = new Cat();
// 仍然是通过 ClassLoader 类加载 Cat 类的 Class 对象
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
// Returns the class's class loader, or null if none.
static ClassLoader getClassLoader(Class<?> caller) {
// This can be null if the VM is requesting it
if (caller == null) {
return null;
}
// Circumvent security check since this is package-private
return caller.getClassLoader0();
}
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
- 对于某个类的 Class类对象,在内存中只有一份,因为类只加载一次
// 对于某个类的 Class 对象,在内存中只有一份,因为类只加载一次
Class<?> aClass = Class.forName("com.hspedu.Cat");
Class<?> aClass1 = Class.forName("com.hspedu.Cat");
Class<?> aClass2 = Class.forName("com.hspedu.Dog");
System.out.println(aClass.hashCode()); // 666641942
System.out.println(aClass1.hashCode()); // 666641942
System.out.println(aClass2.hashCode()); // 960604060
-
每个对象实例都会记得自己是由哪个 Class 实例所生成
-
通过 Class 对象可以完整地得到一个类的完整结构【通过一系列 API】
-
Class 对象是存放在堆的
-
类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据,包括:
-
方法代码
-
变量名
-
方法名
-
访问权限等
-
在类加载完之后
- 除了会生成一个 Class 类对象在堆里面 【其实是一种数据结构,更利于数据的操作】
- 同时在方法区生成类的字节码二进制数据(元数据)
Class类常用方法
方法名 | 功能 |
---|---|
static Class forName(String name) | 返回指定类名 name 的 Class 对象 |
Object newInstance() | 调用默认构造函数,返回该 Clas对象的一个实例 |
getName() | 返回此 Class 对象所表示的实体(类、接口、数组类、基本类型等)名称 |
Class[] getInterfaces() | 获取当前 Class 对象的接口 |
ClassLoader getClassLoader() | 获取该类的类加载器 |
Class getSuperclass() | 返回表示此 Class 所表示的实体的超类 |
Constructor[] getConstructors() | 返回一个包含某些 Constructor 对象的数组 |
Field[] getDeclaredFileds() | 返回 Field 对象的一个数组 |
Method getMethod(String name, Class ..., paramTypes) | 返回一个 Method 对象,此对象的形参类型为 paramType |
public class Car {
public String brand = "宝马";
public int price = 500000;
public String color = "白色";
@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", price=" + price +
", color='" + color + '\'' +
'}';
}
}
// 演示 Class 类地常用方法
public class Class02 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
String classAllPath = "com.hspedu.Car";
// 1. <?> 表示不确定的 java 类型【可能是 Dog、Car、Cat】
Class<?> aClass = Class.forName(classAllPath); // 获取到 Car类 对应的 Class 对象
// 2. 输出 aClass
System.out.println(aClass); // 显示 cls对象,是哪个类的 Class 对象 com.hspedu.Car
System.out.println(aClass.getClass()); // 输出 aClass 运行类型:java.lang.Class
// 3. 得到包名
System.out.println(aClass.getPackage().getName());
// 4. 得到全类名
System.out.println(aClass.getName());
// 5. 通过 aClass 创建实例
Object o = aClass.newInstance();
Car car = (Car) o;
System.out.println(car);
// 6. 通过反射获取属性
Field brand = aClass.getField("brand");
System.out.println(brand.get(car)); // 正常 对象.属性 通过反射:变成 属性.get(对象)
// 7. 通过反射给属性赋值
brand.set(car, "奔驰");
System.out.println(brand.get(car));
System.out.println("========获取所有字段如下=========");
// 8. 我希望大家可以得到所有的属性(字段)
Field[] fields = aClass.getFields();
for (Field field : fields) {
System.out.println(field.getName());
}
}
}
获取 Class 对象的 六种方式
Java程序在计算机有三个阶段
- 代码阶段 / 编译阶段
- Class 类阶段(加载阶段)
- Runtime运行阶段
在不同的阶段,可以用不同的方法得到 Class 类对象
// 演示得到 Class 对象的各种方式 【6种】
public class GetClass_ {
public static void main(String[] args) throws ClassNotFoundException {
// 1. 已知一个类的全类名,且在该类路径下,可通过 Class 类的静态方法 forName() 获取
// 可能抛出 ClassNoFoundException
// 应用场景:多用于配置文件,读取类全路径,加载类 【框架用得比较多】
String classAllPath = "com.hspedu.Car"; // 通过读取配置文件获取
Class<?> aClass = Class.forName(classAllPath);
System.out.println(aClass);
// 2. 类名.class
// 应用场景:多用于参数传递,比如:通过反射得到对应得构造器对象
Class aClass2 = Car.class;
System.out.println(aClass2);
// 3. 已知某个类得实例,调用该实例得 getClass() 方法获取 Class 对象 【就是 运行类型】
Car car = new Car();
Class<? extends Car> aClass3 = car.getClass();
System.out.println(aClass3);
// 4. 通过类加载器 【4种】来获取到类得 Class 对象
// (1) 先得到类加载器 car
ClassLoader classLoader = car.getClass().getClassLoader();
// (2) 通过类加载器得到 Class 对象
Class<?> aClass1 = classLoader.loadClass(classAllPath);
System.out.println(aClass1 );
// aClass、aClass1、aClass2、aClass3
// 对于一个类,只能有一个 Class 类对象
System.out.println(aClass.hashCode());
System.out.println(aClass1.hashCode());
System.out.println(aClass2.hashCode());
System.out.println(aClass3.hashCode());
// 5. 基本数据(int、char、boolean、float、double、byte、long、short)按如下方式得到 Class类对象
// Class cls = 基本数据类型.class
Class<Integer> integerClass = int.class; // 它知道 int 对应得包装类为:Integer 【自动装箱、自动拆箱得过程】
Class<Character> characterClass = char.class;
Class<Boolean> booleanClass = boolean.class;
System.out.println(integerClass);
System.out.println(characterClass);
System.out.println(booleanClass);
// 6. 基本数据类型对应得包装类,可以通过 .TYPE 得到 Class 对象
Class<Integer> type1 = Integer.TYPE;
Class<Character> type2 = Character.TYPE;
System.out.println(type1);
System.out.println(type2 );
System.out.println(integerClass.hashCode());
System.out.println(type1.hashCode());
}
}
哪些类型有 Class 对象
- 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
- interface:接口
- 数组
- enum:枚举
- annotation:注解
- 基本数据类型
- void
// 演示哪些类型有 Class 对象
public class AllTypeClass {
public static void main(String[] args) {
Class<String> stringClass = String.class;// 外部类
Class<Serializable> serializableClass = Serializable.class; // 接口
Class<Integer[]> aClass = Integer[].class; // 数组
Class<Integer[][]> aClass1 = Integer[][].class; // 二维数组
Class<Deprecated> deprecatedClass = Deprecated.class; // 注解
Class<Thread.State> stateClass = Thread.State.class; // 枚举 (enum)
Class<Long> longClass = long.class; // 基本数据类型
Class<Integer> type = Integer.TYPE; // 包装类
Class<Void> voidClass = void.class; // void 返回类型
Class<Class> classClass = Class.class; // 外部类 【万物皆对象】
}
}
3. 类加载
反射机制是 java 实现动态语言得关键,也就是通过 反射 实现类动态加载
- 静态加载:编译时,加载相关得类,如果没有则报错,依赖性太强
- 动态加载:运行时加载需要得类,如果运行时不用该类,即使不存在,也不报错,降低了依赖性
// 静态加载举例 【使用日记本写代码】
import java.util.*;
public class ClassLoad_ {
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
System.out.println("请输入key:");
String key = scanner.next();
switch(key){
case "1":
Dog dog = new Dog(); // 在运行Dog 不一定会用到,但是它不管那么多,在编译时就会加载这个类,并且进行语法的校验
dog.cry();
break;
case "2":
System.out.println("ok");
break;
default:
System.out.println("do nothing");
}
}
}
// 动态加载举例 【使用日记本写代码】
import java.lang.reflect.*;
import java.util.*;
public class ClassLoad_ {
public static void main(String[] args) throws Exception{
Scanner scanner = new Scanner(System.in);
System.out.println("请输入key:");
String key = scanner.next();
switch(key){
case "1":
// Dog dog = new Dog(); 静态加载【依赖性很强】
// dog.cry();
break;
case "2":
// 反射 ---> 动态加载
Class cls = Class.forName("Person"); // 加载 Person 类 【此时,在编译时,不会加载这个类】 而是在执行到这段代码时,它才会去加载。因此降低了依赖性
Object o = cls.newInstance();
Method m = cls.getMethod("hi");
m.invoke(o);
System.out.println("ok");
break;
default:
System.out.println("do nothing");
}
}
}
Person类是动态加载,所以,没有编写 Person 类也不会报错,只有当动态加载该类时,才会报错
类加载时机
- 当创建对象时 (new:就是静态加载)
- 当子类被创建时,父类也加载(静态)
- 调用类中的静态成员时(静态)
- 通过反射(动态)
类加载流程图
类加载分为 3 个阶段
- 加载 Loading
- 连接 Linking
- 验证 verification:安全校验
- 准备 Preparation:对静态变量进行默认初始化,并分配空间,
- 解析 Resolution:将符号引用转为直接引用
- 初始化 initialization
类加载的3个阶段
1. 加载 Loading
JVM在该阶段的主要目的是将字节码从不同数据源(可能是 class 文件、也可能是 jar 包、甚至网络)转化为
- 二进制字节流加载到内存中(方法区中完成)
- 并生成一个代表该类的 java.lang.Class 对象(在堆中完成)---> 以二进制字节流为标准
2. 连接 Linking
-
验证
-
目的是为了确保 Class 文件的1字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
-
包括:
- 文件格式验证(是否以魔数 oxcafebabe 开头)、元数据验证、字节码验证和符号引用验证
// cafebabe 类文件命名格式 // cafedead 持久对象命名格式 // 高斯林的故事
你知道所有的java class文件都有一个相同的4字节串吗。这个4字节串的16进制是CAFEBABE // 4 * 8 / 4 = 8个 根据白皮书的规范, // cafe babe 0000 003a 005a 0a00 0200 0307 前四个字节(u4)cafebabe就是Class文件的魔数, 第5、6字节(u2)是Class文件的次版本号, 第7、8字节(u2)是主版本号。十六进制0和3a,也就是版本号为58.0。 We used to go to lunch at a place called St Michael's Alley. According to local legend, in the deep dark past, the Grateful Dead used to perform there before they made it big. It was a pretty funky place that was definitely a Grateful Dead Kinda Place. When Jerry died, they even put up a little Buddhist-esque shrine. When we used to go there, we referred to the place as Cafe Dead. Somewhere along the line it was noticed that this was a HEX number. I was re-vamping some file format code and needed a couple of magic numbers: one for the persistent object file, and one for classes. I used CAFEDEAD for the object file format, and in grepping for 4 character hex words that fit after "CAFE" (it seemed to be a good theme) I hit on BABE and decided to use it. At that time, it didn't seem terribly important or destined to go anywhere but the trash-can of history. So CAFEBABE became the class file format, and CAFEDEAD was the persistent object format. But the persistent object facility went away, and along with it went the use of CAFEDEAD - it was eventually replaced by RMI. 以前我们曾去过一个叫StMichael's Alley的地方吃中午饭。根据当地一个传说,在很久很久以前,一个叫做Grateful Dead的乐队在成名之前一直都在这里表演。当乐队成员Jerry死了之后,这里还一度变成纪念他的地方。我注意到这个地方写着一串16进制的魔术(这尼玛明明是个咖啡馆的名字好吗)。当时我正在改造一些文件的格式,我当时需要2个魔术:一个是用来表示object file,另外一个用来表示class文件。我选择使用CAFEDEAD来表示object file,然后把后面2个字节换掉之后用来表示class文件。我突然想到BABE(小孩的意思)这个放在CAFE的后面应该不错。当时看来这个东西好像并不是特别重要啦~所以,结果就是你们现在看到的CAFEDEAD用来表示持久化的object format(也就是object序列化之后的结果),然后CAFEBABE就用来表示class文件。当时persistence object的最终被RMI(java远程调用)替换掉了,CAFEDEAD也就不复存在了。 0xCAFEBABE的十进制是3405691582。如果我们把所有位加起来得到43。恰好大于42-Ultimate Answer to the Life, the Universe, and Everything。另外43也是一个质数。You see, magic everywhere. Even in the last sentence.
-
可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间
-
-
准备
-
JVM会在该阶段堆静态变量,分配内存并默认初始化(对应数据类型的默认初始值。如:0、0L、null、false等),这些变量所使用的内存都将在方法区中进行分配
class A{ // 属性 --- 成员变量 --- 字段 // 老韩分析 类加载的链接阶段- 准备 属性是如何处理的 // 1. n1 是实例属性,不是静态变量,因此在准备阶段,是不会分配内存 // 2. n2 是静态变量,分配内存 n2 默认初始化:0,而不是 20 // 3. n3 是 final static 是常量,它和静态变量不一样,因为一旦赋值就不变 n3 = 30 public int n1 = 10; public static int n2 = 20; public static final int n3 = 30; }
-
-
解析
-
虚拟机将常量池内的符号引用替换为直接引用的过程
【在编译阶段因为无法确定真实的内存地址,所以只是简单的符号引用;但是到了内存中,就会有了真正的内存地址了,就可以靠地址来相互引用,变为直接引用】
-
3. 初始化(程序员控制的,之前都是 JVM 来控制)
-
到初始化阶段,才真正开始执行类中定义的 Java 程序代码,此阶段是执行 <clinit>() 方法的过程
-
<clinit>() 方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有 静态变量 的赋值动作和 静态代码块 中的语句,并进行合并
// 演示类加载 --- 初始化阶段 public class ClassLoad03 { public static void main(String[] args) { // 分析 // 1. 加载 B 类,并生成 B 的 Class 对象 // 2. 连接 num = 0 // 3. 初始化阶段 // 依次自动收集类中的所有 静态变量 的赋值动作和 静态代码块 中的语句,并进行合并 /* clinit(){ System.out.println("B 的静态代码块被执行"); num = 300; int num = 100 } 合并后:num = 100 */ System.out.println(B.num); // 100 如果直接使用类的静态属性,也会导致类的加载 } } class B{ static { System.out.println("B 的静态代码块被执行"); num = 300; } static int num = 100; public B(){ // 构造器 System.out.println("B()的构造器被执行"); } }
-
虚拟机会保证一个类 <clinit>() 方法在多线程环境中正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的 <clinit>() 方法,其它线程需要阻塞等待,直到活动线程执行 <clinit>() 方法完毕
protected Class<?> loadClassOrNull(String cn, boolean resolve) { // 正因为有这个机制,才能保证某个类在内存中,只有一份 Class 对象 synchronized (getClassLoadingLock(cn)) { // check if already loaded Class<?> c = findLoadedClass(cn); if (c == null) { // find the candidate module for this class LoadedModule loadedModule = findLoadedModule(cn); if (loadedModule != null) { // package is in a module BuiltinClassLoader loader = loadedModule.loader(); if (loader == this) { if (VM.isModuleSystemInited()) { c = findClassInModuleOrNull(loadedModule, cn); } } else { // delegate to the other loader c = loader.loadClassOrNull(cn); } } else { // check parent if (parent != null) { c = parent.loadClassOrNull(cn); } // check class path if (c == null && hasClassPath() && VM.isModuleSystemInited()) { c = findClassOnClassPathOrNull(cn); } } } if (resolve && c != null) resolveClass(c); return c; } }
类加载完之后,在方法区中就会有类的字节码二进制数据(元数据),但是不利于操作。
为了方便操作,在堆中还会生成 该类字节码对应的 类的 Class 对象 【一种数据结构:数据的操作入口】,操作更方便
4. 反射获取类的结构信息
class A{
public String hobby;
public void hi(){}
public A(){}
}
interface IA{}
interface IB{}
@Deprecated
class Person extends A implements IA, IB {
// 属性
public String name;
protected int age;
String job;
private double sal;
// 方法
public void m1() {
}
protected void m2() {
}
void m3() {
}
private void m4() {
}
public Person(){}
public Person(String name){}
// 私有构造器
private Person(String name, int age){}
}
Class
Class<?> personCls = Class.forName("com.hspedu.reflection.question.Person");
// getName: 得到全类名
System.out.println(personCls.getName()); // com.hspedu.reflection.question.Person
// getSimpleName:获取简单类名
System.out.println(personCls.getSimpleName()); // Person
Field
// getFields():获取所有 public 修饰的属性,包含本类以及父类的
System.out.println("public属性如下:");
Field[] fields = personCls.getFields();
for (Field field : fields) {
System.out.println("本类以及父类的属性 " + field.getName()); // 只能得到 public 修饰的属性
}
System.out.println("=========================");
System.out.println("所有属性如下");
// getDeclaredFields:获取本类中的所有属性
Field[] declaredFields = personCls.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("本类以及父类中的所有属性 " + declaredField.getName());
}
java.lang.reflect.Field类
-
getModifiers:以 int 形式返回修饰符 【最终返回累计值】
-
默认修饰符是 0
-
public:1
-
private:2
-
protected:4
-
static:8
-
final:16
Class<?> personCls = Class.forName("com.hspedu.reflection.question.Person"); System.out.println("所有属性如下"); // getDeclaredFields:获取本类中的所有属性 Field[] declaredFields = personCls.getDeclaredFields(); for (Field declaredField : declaredFields) { System.out.println("本类以及父类中的所有属性 " + declaredField.getName() + "该属性的修饰符值 = " + declaredField.getModifiers()); } 所有属性如下 本类以及父类中的所有属性 name该属性的修饰符值 = 1 本类以及父类中的所有属性 age该属性的修饰符值 = 4 本类以及父类中的所有属性 job该属性的修饰符值 = 0 本类以及父类中的所有属性 sal该属性的修饰符值 = 2
-
-
getType:以 Class 形式返回类型
// 返回属性对应的那个类的 Class 对象 for (Field declaredField : declaredFields) { System.out.println("本类以及父类中的所有属性 " + declaredField.getName() + "该属性的修饰符值 = " + declaredField.getModifiers() + "该属性的类型 = " + declaredField.getType()); } 本类以及父类中的所有属性 name该属性的修饰符值 = 1该属性的类型 = class java.lang.String 本类以及父类中的所有属性 age该属性的修饰符值 = 4该属性的类型 = int 本类以及父类中的所有属性 job该属性的修饰符值 = 0该属性的类型 = class java.lang.String 本类以及父类中的所有属性 sal该属性的修饰符值 = 2该属性的类型 = double
-
getName:返回属性名
Method
// getMethods:获取所有 public 修饰的方法,包含本类以及父类[包含 Object]的
System.out.println("public 修饰的方法如下: ");
Method[] methods = personCls.getMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
System.out.println("所有方法如下");
// getDeclaredMethods:获取本类中的所有方法
Method[] declaredMethods = personCls.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredFields.getClass());
}
- getModifiers:以 int 形式返回修饰符 【最终返回累计值】
- 默认修饰符是 0
- public:1
- private:2
- protected:4
- static:8
- final:16
- getReturnType:以 Class 形式获取 返回类型
- getName:获取方法名
- getParameterTypes:以 Class[] 返回参数类型数组
// getDeclaredMethods:获取本类中的所有方法
Method[] declaredMethods = personCls.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod.getName() +
"该方法的访问修饰符值" + declaredMethod.getModifiers() +
"该方法的返回类型" + declaredMethod.getReturnType());
所有方法如下
m2该方法的访问修饰符值4该方法的返回类型void
m1该方法的访问修饰符值1该方法的返回类型void
m3该方法的访问修饰符值0该方法的返回类型void
m4该方法的访问修饰符值2该方法的返回类型void
// 方法
public void m1(String name, int age, double sal) { // m1() 方法有 3个形参
}
当方法比较多的时候,点开 Structure 可以快速定位方法
// 输出当前这个方法的形参数组情况
Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.println("该方法的形参类型=" + parameterType);
}
该方法的形参类型=class java.lang.String
该方法的形参类型=int
该方法的形参类型=double
Constructor
// getConstructors:获取所有 public 修饰的构造器,本类
Constructor<?>[] constructors = personCls.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor.getName());
}
System.out.println();
System.out.println("输出所有构造器");
// getDeclaredConstructors:获取本类所有的 构造器
Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor.getName()); // 这里只是输出名字
}
- getModifiers:以 int 形式返回修饰符
- getName:返回构造器名(全类名)
- getParameterTypes: 以 Class[] 返回参数类型数组。
// getDeclaredConstructors:获取本类所有的 构造器
Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println("=========================");
System.out.println("本类中所有构造器" + declaredConstructor.getName()); // 这里只是输出名字
Class<?>[] parameterTypes = declaredConstructor.getParameterTypes(); // Class[] 返回参数类型数组
for (Class<?> parameterType : parameterTypes) {
System.out.println("该构造器的形参类型= " + parameterType);
}
}
interface
// getInterfaces:以 Class[] 形式返回接口信息
Class<?>[] interfaces = personCls.getInterfaces();
for (Class<?> anInterface : interfaces) {
System.out.println("接口信息= " + anInterface);
}
Annotatinos
// getAnnotations:以 Annotation[] 形式返回注解信息
Annotation[] annotations = personCls.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("注解信息为: " + annotation);
}
包信息
// getPackage:以 Package 形式 返回 包信息
System.out.println(personCls.getPackage()); // com.hspedu.reflection.question【类所在的包】
得到父类 Class 对象
// getSuperClass:以 Class 形式返回父类信息
Class<?> superclass = personCls.getSuperclass();
System.out.println("父类的对象= " + superclass);
5. 通过反射创建对象
5.1 调用类中的 public 修饰的无参构造器
通过 class 对象
// 方式1:通过反射创建某类的对象,要求该类中必须有 public 的无参构造
Class<?> aClass = Class.forName("com.hspedu.User");
Object o = aClass.newInstance();
System.out.println(o);
5.2 调用类中的指定构造器
// 公有构造器
Constructor<?> constructor = aClass.getConstructor(String.class);
Object o2 = constructor.newInstance("11");
System.out.println(o2);
5.3 Class类相关方法
-
newInstance:调用类中的无参构造器(public),获取对应类的对象
-
getConstructor(Class... clazz):根据参数列表,获取对应的 public 构造器对象
// (Class... clazz) 表示:可以传入多个 Class 对象
-
getDeclaredConstructor(Class...clazz):参数列表,获取对应的 所有 构造器对象
5.4 Constructor类相关方法
- setAccessible:爆破
- newInstance(Object...obj):调用构造器【指定构造器传入的实参】
// 私有构造器
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(int.class, String.class);
System.out.println(declaredConstructor);
declaredConstructor.setAccessible(true); // 爆破【暴力破解】:使用反射,访问私有的构造器,反射面前都是纸老虎
Object o1 = declaredConstructor.newInstance(10, "hi");
System.out.println(o1);
测试:
- 通过反射创建某类的对象,要求该类中必须有 public 的无参构造
- 通过调用某个特定构造器的方式,实现创建某类的对象
6. 通过反射访问属性
- 根据属性名获取 Field 对象
Field f = clazz对象.getDecalredField(属性名)
- 爆破:
f.setAccessible(true); // f 是 Field
- 访问
f.set(o, 值); // o 表示对象
syso(f.get(o)); // o 表示对象
- 注意:如果是静态属性,则 set 和 get 中的参数 o,可以写成 null【静态属性是和类相关的,而不是和对象相关的】
public class Student {
public int age;
private static String name;
public Student(){}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name=" + name +
'}';
}
}
public class ReflectAccessProperty {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException {
Class<?> aClass = Class.forName("com.hspedu.Student");
Object o = aClass.newInstance(); // 创建对象
// 通过反射得到 age 属性对象
Field age = aClass.getField("age");
age.set(o, 11); // 通过反射来操作属性
System.out.println(o);
System.out.println(age.get(o)); // 返回 age 属性的值
// 使用反射操作 name 属性 【私有的,静态的】
Field name = aClass.getDeclaredField("name");
name.setAccessible(true);
name.set(null, "ACLQ"); // 因为 name 是 static 属性,因此 o 也可以写成 null
System.out.println(o);
// 访问属性值
System.out.println(name.get(o));
System.out.println(name.get(null)); // 因为是 static
}
}
7. 通过反射访问方法
-
根据方法名和参数列表获取 Method方法对象:
Method m = clazz.getDeclaredMethod(方法名, XX.class)
-
获取对象
Object o = clazz.newInstance();
-
爆破
m.setAccessible(true);
-
访问
Object returnVlaue = m.invoke(o, 实参列表); // o 就是对象
-
注意
如果是静态方法,则invoke 的参数 o ,可以写成 null
public class Boss {
public int age;
private static String name;
public Boss(){}
private static String say(int n, String s, char c){
return n + " " + s + " " + c;
}
public void hi(String s){
System.out.println("hi" + s);
}
}
public class ReflectAccessMethod {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<?> aClass = Class.forName("com.hspedu.Boss");
Object o = aClass.newInstance();
Method hi = aClass.getMethod("hi", String.class);
System.out.println(hi.invoke(o, "232")); // hi() 是普通方法,一定要有一个实例,不能写 null
// 调用 private static 方法
Method say = aClass.getDeclaredMethod("say", int.class, String.class, char.class);
say.setAccessible(true); // 爆破
Object invoke = say.invoke(o, 1, "24", '1'); // 因为 hi() 是 static,可以把 o 换成 null
System.out.println(invoke);
// 在反射中,如果方法有返回值,统一返回 Object
// 编译类型是 Object
// 运行类型是 String
Object invoke1 = say.invoke(null, 300, "王五", '男');
System.out.println(invoke1.getClass());
}
}
8. 课后习题【获取私有变量,得到之后,调用 setAccessible(true) ==> 爆破!】
- 通过反射修改私有成员变量
public class PrivateClass {
private String name = "helloKitty";
public String getName() {
return name;
}
}
public class homework01 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> aClass = Class.forName("com.hspedu.PrivateTest"); // 或者 PrivateTest.class.var
Object o = aClass.getConstructor().newInstance();
Field name = aClass.getDeclaredField("name");
name.setAccessible(true);
name.set(o, "Kitty");
Method getName = aClass.getMethod("getName");
System.out.println(getName.invoke(o));
}
}
- 利用反射和 File 完成以下功能
- 利用 Class 的 forName 方法得到 File类的class 对象
- 在控制台打印 File 类的所有构造器
- 通过 newInstance的方法创建 File 对象看,并创建 E:/mynew.txt 文件
// 提示:创建文件的正常写法如下:
File file = new File("D:\\aa.txt");
file.createNewFile();
public class homework02 {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
Class<File> fileClass = File.class;
Constructor<?>[] declaredConstructors = fileClass.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
// 指定得到 public java.io.File(java.lang.String) 构造器
File file = fileClass.getConstructor(String.class).newInstance("E:/aac.txt"); // file 只是对象,还在内存中
// 得到 createNewFile 的方法对象
Method createNewFile = fileClass.getMethod("createNewFile");
// 利用反射调用方法
createNewFile.invoke(file); // 创建文件,调用的是 createNewFile 【public ,没有形参】
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix