文章目录
- 反射机制的作用
- 反射机制的原理
- 加载机制详细解释
- 获取 Class 对象
- 反射获取构造方法:获取 Class 对象里面 Constructor 对象
- 反射获取成员变量:获取Class 对象里面的 Field 对象
- 反射获取成员方法:获取 Class 对象里的 Method 对象
- 其他常用 API
前言
反射非常重要,一定要掌握,我个人觉得不是很难,就是要注意各种方法里的参数是什么,然后还是那句话:最快的入门方式是看视频,建议先看完视频,再来看博客,这里我推荐 b 站韩顺平老师的视频
反射机制的作用
作用看不懂的话可以最后看
反射可以在不修改源码的情况下来控制程序符合 ocp 原则 :不修改源码,扩容功能
举个例子
现在有一个 main 类 和 Person 类,还有一个配置文件my.Properties,可以利用反射通过中间的 my.Properties 访问类中的东西
my.Properties
Classfullpath = com.mangfu.Person
field = name
Person
public class Person {
public String name = "jack";
public int age = 13;
public Person() {};
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
Main
public class Main {
public static void main(String[] args) throws Exception{
//导入配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src/com/mangfu/my.Properties"));
//通过配置文件获取 类的完整路径, 和类的属性名
String classfullpath = properties.getProperty("Classfullpath");
String field = properties.getProperty("field");
//通过反射获取到 Person类的字节码对象,我们可以操作里面所有的东西
Person person = new Person();
Class clazz = Class.forName(classfullpath);
//这里直接操作类里面的属性
//本来是要 person.name 改成 person.age
//现在直接在配置文件中的 name 改成 age就行了
Field field1 = clazz.getField(field);
System.out.println(field1.get(person));
}
}
可以看见我们可以通过中间的 配置文件 无需修改源码,就能操作类中的信息
反射机制的原理
编译的时候会产生字节码文件,然后通过类加载器,进入加载阶段,会在堆区创建一个 Class 类的对象,当然这个Class对象里面的成员变量,构造器,构造方法等东西都是对象,然后 到 new Cat(),在 堆区创建 Cat 对象,该对象知道他是属于哪个 Class 对象,我们可以通过反射得到这个 Class 对象,然后操作类中的所有东西。
注意:一个类只有一个 Class 对象
加载机制详细解释
类加载过程图
Loading(加载)
JVM 将 字节码文件 从 不同 的 数据源,转化为 二进制字节流,加载到内存中,生成一个代表该类的 Class 对象,也就是这个类的对象
verification(验证)
为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,,不会危害虚拟机自身的安全,这个阶段会 进行文件格式验证 **(是否以魔数 oxcafebabe开头),元数据验证,字节码验证,和符号引用验证
如果想要关闭大部分类验证措施,加快加载速度可以这样做
javac -Xverify:none HelloWorld.java
helloworld 是类名
Preparation(准备)
JVM 会在该阶段 对 静态变量!!!,分配内存并初始化(对应数据类型的默认初始值)。这些变量的 内存分配在方法区
注意:这里是静态变量,不是静态的,加载阶段不会分配内存
Resolution (解析)
JVM 将常量池的 == 符号引用== 替换为 直接引用的过程
符号引用
将 Java 类文件中,以一组符号来描述类名,方法名等东西,这些符号与实际的内存地址没有关系
直接引用
将符号引用,直接替换为直接指向这些数据的指针或地址
还没分配内存的时候用符号引用类似于打个标记,这个时候分配内存在变成直接引用
initialization(初始化)
这个阶段才真正执行类中定义的 Java 程序代码,这个阶段也是执行 clinit() 方法的过程,这个方法会根据顺序,把静态变量的赋值动作,和静态代码块中的语句合并
clinit 方法在多线程的环境中会正确加锁,也就是线程是阻塞的,等一个线程 clinit 方法执行完,才放锁,让下一个线程用
获取 Class 对象
- Class.forName(“全类名”):编译阶段创建
全类名:包名 + 类名
多用于配置文件,最常用
//com.mangfu.test 包下的 Student 类
Class test = Class.forName("com.mangfu.test.Student" );
- 类名.class:加载阶段创建,类加载器得到 Class 对象
多用于参数传递,比如通过反射得到对应构造器对象,这种方式 最安全,性能最高
Class clazz = Student.class
- 对象.getClass():运行阶段创建
通过创建好的对象,获取 Class 对象
Student s = new Student();
Class clazz = s.getClass();
- 基本数据类型.class
基本数据类型,创建 Class 对象的方法
Class clazz = int.class
- 包装类.TYPE
基本数据类型对应的包装类,创建 Class 对象的放啊
Class clazz = Integer.class
反射获取构造方法:获取 Class 对象里面 Constructor 对象
Class类 中用于获取构造方法的方法
- Constructor<?>[] getConstructors(): 返回所有公有构造方法对象的数组
- Constructor<?>[] getDeclaredConstructors():返回所有构造方法对象的数组
- Constructor getConstructor(Class<?>… parameteTypes):返回单个公有构造方法对象
参数为 基本数据结构或者包装类的字节码,对象.class,这样可以获取有参构造
- Constructor getDeclaredConstructor(Class<?>…parmeterTypes):返回单个构造方法对象
参数为 基本数据结构或者包装类的字节码,对象.class,这样可以获取有参构造
Constructor:用构造方法对象创建对象的方法
Student类
- T newinstance(Object…initargs):根据指定的构造方法创建对象
参数(可变):是构造函数里面需要的参数
- setAccessible(boolean flag):设置为 true, 表示取消访问检查
setAccessible:**使用反射可以访问 private 构造器 [称为爆破]
举个例子
主要看Main类
Student类
public class Student {
private int age = 18;
public String name = "张三";
private Student() {}
private Student(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
Main 类
public class Main {
public static void main(String[] args)throws Exception {
Class clazz = Class.forName("com.mangfu.test.Student");
//因为 构造方法是私有的 所以我们要用 getDeclaredConstructor
//里面的参数是 形参的字节码
Constructor cs = clazz.getDeclaredConstructor(int.class, String.class);
//因为这个是私有对象,我们要通过爆破创建才能构造一个对象
cs.setAccessible(true);
//通过 newInstance 构造一个对象
Student s = (Student) cs.newInstance(18, "张三");
System.out.println(s);
}
}
反射获取成员变量:获取Class 对象里面的 Field 对象
Class 类中用于获取成员变量的方法
- Field[] getFields():返回所有公共成员变量对象的数组
- Field[] getDeclaredFields():返回所有成员变量对象的数组
- Field[] getField(String name):返回单个公有成员变量对象
参数是 对象(不是Class对象,下面是Student 对象) 的属性名
- Field getDeclaredField(String name):返回单个成员变量对象
参数是 对象(不是Class对象,下面是Student 对象) 的属性名
Field 类中用于创建对象的方法
- void set(Object obj, Object value):赋值
参数1:field 对象,参数2:需要赋的值
- Object get(Object obj):获取值
参数:对象(不是Class 对象,下面是 Student 对象)的名字
- setAccessible(boolean flag):设置为 true, 表示取消访问检查
举例
Student 类
public class Student {
private int age = 18;
private String name = "张三";
private Student() {}
private Student(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
Main类
public class Main {
public static void main(String[] args)throws Exception {
//得到 Student 类的 Class 对象
Class clazz = Class.forName("com.mangfu.test.Student");
//获取 Class 对象里面的 Field 对象----属性 name
Field field = clazz.getDeclaredField("name");
//通过 反射利用构造函数构造对象
Constructor cs = clazz.getDeclaredConstructor(int.class, String.class);
cs.setAccessible(true);
Student s = (Student) cs.newInstance(18, "张三");
//因为这个属性是私有的 所以要用 setAccessible 爆破一下,才能修改
field.setAccessible(true);
//修改 属性 name 的 值,因为上面我们获取的 是属性 name 对象
field.set(s, "李四");
System.out.println(field.get(s));//输出李四
}
}
反射获取成员方法:获取 Class 对象里的 Method 对象
Class类中用于获取成员方法的方法
- Method[] getMethods():返回公有成员方法对象的数组,包括继承的
- Method[] getDeclaredMethods:返回所有成员方法对象的数组,不包括继承的
- Method getMethod(String name, Class<?>…parameterTypes):返回单个公有成员方法对象
参数1:方法的名字,参数2:方法的形参(可变)
- Method getDeclaredMethod(String name, Class<?>…parameterTypes):返回单个成员方法对象
参数1:方法的名字,参数2:方法的形参(可变)
Method类中用于创建对象的方法
- Object invoke(Object obj, Object…args):允许方法
参数1: 对象(不是Class对象,下面是Student 对象),参数2:调用方法要传递的参数(没有就不写)。返回值:调用的方法的返回值的对象
- setAccessible(boolean flag):设置为 true, 表示取消访问检查
举例
Student 类
public class Student {
private int age = 18;
private String name = "张三";
public Student() {};
private Student(int age, String name) {
this.age = age;
this.name = name;
}
private int hi() {
System.out.println("调用hi函数");
return 1;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
Main 方法
public class Main {
public static void main(String[] args)throws Exception {
//得到 Student 类的 Class 对象
Class clazz = Class.forName("com.mangfu.test.Student");
//得到 Student 中的 hi 方法的对象
Method method = clazz.getDeclaredMethod("hi");
//这里 通过反射 构造 Student 类对象
Constructor constructor = clazz.getConstructor();
Student s = (Student) constructor.newInstance();
//通过反射调用 Student 类中的 hi 方法,因为我们上面的得到的 Method 对象是 hi 方法
//而且这个方法是私有的所以要爆破一手
method.setAccessible(true);
method.invoke(s);
}
}
其他常用 API
Class
Constructor
Parmeter[] getrParmameters():获取构造方法的形参
int getmodifiers():获取成员变量的数据类型
Field
int getmodifiers():获取成员变量的数据类型
Class<?> getType:获取成员变量的数据类型
String getName:获取成员变量的名字
Method
int getmodifiers():获取成员变量的数据类型
Class[] getExceptionTypes:获取方法抛出的异常
String getName:获取成员变量的名字
Class[] getParameter():获取方法的形参,以 Class[] 形式返回
Class getReturnType:以Class 形式获取返回类型
getmodifiers() 方法返回值
默认修饰符号:0,public:1,private:2,protected:3,static:8,final:16