刚开始学习java时,反射是一个比较新的概念,不太理解反射是什么,学习的次数多了也慢慢知道了反射的概念,视频教程总是讲概念和使用,却未曾讲过反射的原理,所以对其一直是一知半解,知道最近学习框架,接触的多了也就对反射有了更加深刻的一些认识,特此记录。
反射的概念:
反射就是Reflection,Java的反射是指程序在运行期可以拿到一个对象的所有信息。
一句话就描述清楚了反射可以做的事情,那么剩下的故事就位置这一句话展开了,我们不理解反射的原因大多是不知道反射的应用场景,没有利用反射编写程序,所以对其理解不深,那么如何才能加深理解呢,接着向下了解吧。
关于反射我的理解,先从“反射”这个词聊起,反射的含义在java里指的是通过字节码对象反过来获取类的信息,我们一般是将一个类初始化为一个对象,从而得到类的字节码,而反射做的事情就是,从字节码得到类的信息,所以叫做反射,就像我们照镜子一样,通过镜子反射出我们自己。类编译,变成字节码,然后通过字节码变成类,这个过程就是反射。
那么我们能够在运行的时候拿到一个类的信息有什么好处呢,一般我们写程序的时候初始化一个类都是指定了一个类名,来初始化一个类,在编写程序的时候用硬编码的方式初始化了一个类的对象,而反射可以做什么呢,字节码这个概念是在jvm中的,是运行中的程序才会有字节码,如果我们能够在运行的时候通过字节码得到了类的信息,那么我们就能够在程序运行的过程中动态的对类进行初始化了,此时创建类的方式也就更加灵活了,因为程序的运行过程中的参数我们是不能确定的,我们不能在程序运行之初就将程序所需要的对象全部进行初始化,那么在程序运行的过程中,通过反射创建对象,就会给我们的程序设计带来了更大的灵活性。我们可以将需要初始化的类放在配置文件里,在用户需要时,动态的进行创建,可以节省运行资源。
与反射相关比较重要的类
学习了反射的概念之后我们就需要学习反射一般怎么使用,在网上的视频教程中往往通过一个类的类名,获取某个类的字节码对象,然后创建出类对象,最后演示通过反射调用对象的方法,获取对象的属性,然后就完了,很少有讲其中的原理的,那么我就讲一下自己的理解吧。
第一个与反射相关最重要的类: Class类
要理解反射,首先需要理解的就是Class类,那么Class类是什么呢,学过java的人都知道一句话,学习完Class之后我对这句话更加深信不移,在我的理解Class类就是所有类的抽象,是用来描述所有的类的。所有的类都是Class的一个实例。
我们所定义的java类是对现实生活的实体,或者业务的抽象,比如对狗进行抽象,定义狗的属性,可以分别抽象为,狗的颜色,狗的重量,狗的年龄,定义狗的活动,狗会叫,狗会吃东西,狗会睡觉。我们通过这些属性和方法来定义一个狗。
而Class类,是类的抽象的意思就是,Class类是来定义一个类的,比如一个类所具有的属性就是,类有属性,类有构造方法,有成员方法。有类的名字。Class类就是用来描述每一个类的。
此处讲一部分类加载过程发生的事情,不一定正确,但是流程大概是如此的,方便理解Class类。当我们new一个对象时,如果第一次new这个类的对象,那么jvm就会将这个类的字节码加载到内存中,然后创建一个Class对象指向这个字节码对象,用一个Class实例对象表示某个类。而每个类的Class实例对象都只有一个,同样的类,实例对象只有一个比较容易理解吧,因为描述的都是这个类。类是单一的,只有类初始化的实例化对象是不同的,就像狗这个类,可以初始化很多不同的狗,但是对于具体的一只狗,对其的描述都是一样的。对于同一个类,jvm只会为我们建立一个Class类的实例。
相信有了这些解释,我们已经能够对class类有了一些理解了。
Class类详解
与反射相关的其他类
Java的类反射所需要的类并不多,它们分别是:Field、Constructor、Method、Class,Object,下面我将对这些类做一个简单的说明。
- Field类:提供有关类或接口的属性的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)属性或实例属性,简单的理解可以把它看成一个封装反射类的属性的类。
- Constructor类:提供关于类的单个构造方法的信息以及对它的访问权限。这个类和Field类不同,Field类封装了反射类的属性,而Constructor类则封装了反射类的构造方法。
- Method类:提供关于类或接口上单独某个方法的信息。所反映的方法可能是类方法或实例方法(包括抽象方法)。 这个类不难理解,它是用来封装反射类方法的一个类。
- Class类:类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。
- Object类:每个类都使用 Object 作为超类。所有对象(包括数组)都实现这个类的方法。
了解了Class类之后我们就可以开始反射之旅了
反射的第一步就是获取类的字节码,然后通过字节码对象(class实例)获取类的信息,然后通过获取的类的信息,实例化一个对象,或者对某个对象进行操作。
如果不知道某个对象的确切类型,RTTI可以告诉你,但是有一个前提:这个类型在编译时必须已知,这样才能使用RTTI来识别它。Class类与java.lang.reflect类库一起对反射进行了支持,该类库包含Field、Method和Constructor类,这些类的对象由JVM在启动时创建,用以表示未知类里对应的成员。这样的话就可以使用Contructor创建新的对象,用get()和set()方法获取和修改类中与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。另外,还可以调用getFields()、getMethods()和getConstructors()等许多便利的方法,以返回表示字段、方法、以及构造器对象的数组,这样,对象信息可以在运行时被完全确定下来,而在编译时不需要知道关于类的任何事情。
反射机制并没有什么神奇之处,当通过反射与一个未知类型的对象打交道时,JVM只是简单地检查这个对象,看它属于哪个特定的类。因此,那个类的.class对于JVM来说必须是可获取的,要么在本地机器上,要么从网络获取。所以对于RTTI和反射之间的真正区别只在于:
- 传统RTTI(运行时类型信息),编译器在编译时打开和检查.class文件
- 反射,运行时打开和检查.class文件
通过运用反射完成对一个类的操作,体会一下反射
package cn.zdonliu.java_study_plan.reflectest;
/**
* @className: FatherClass
* @description: TODO 类描述
* @author: zdonliu
* @date: 2022/9/25
**/
public class FatherClass {
public String mFatherName;
public int mFatherAge;
public void printFatherMsg(){}
}
package cn.zdonliu.java_study_plan.reflectest;
/**
* @className: SonClass
* @description: TODO 类描述
* @author: zdonliu
* @date: 2022/9/25
**/
public class SonClass extends FatherClass{
private String mSonName;
protected int mSonAge;
public String mSonBirthday;
public void printSonMsg(){
System.out.println("Son Msg - name : "
+ mSonName + "; age : " + mSonAge);
}
private void setSonName(String name){
mSonName = name;
}
private void setSonAge(int age){
mSonAge = age;
}
private int getSonAge(){
return mSonAge;
}
private String getSonName(){
return mSonName;
}
}
package cn.zdonliu.java_study_plan.reflectest;
/**
* @className: TestClass
* @description: TODO 类描述
* @author: zdonliu
* @date: 2022/9/25
**/
public class TestClass {
//String 会被 JVM 优化
private final String FINAL_VALUE;
public String getFinalValue(){
//剧透,会被优化为: return "FINAL" ,拭目以待吧
return FINAL_VALUE;
}
public TestClass()
{
this.FINAL_VALUE = "FINAL";
}
private String MSG = "Original";
private void privateMethod(String head , int tail){
System.out.print(head + tail);
}
public String getMsg(){
return MSG;
}
}
package cn.zdonliu.java_study_plan.reflectest;
import org.junit.Test;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
/**
* @className: TestReflect
* @description: TODO 类描述
* @author: zdonliu
* @date: 2022/9/25
**/
public class TestReflect {
/**
* 通过反射获取类的所有变量
*/
@Test
public void printFields(){
//1.获取并输出类的名称
Class mClass = SonClass.class;
System.out.println("类的名称:" + mClass.getName());
//2.1 获取所有 public 访问权限的变量
// 包括本类声明的和从父类继承的
//Field[] fields = mClass.getFields();
//2.2 获取所有本类声明的变量(不问访问权限)
Field[] fields = mClass.getDeclaredFields();
//3. 遍历变量并输出变量信息
for (Field field :
fields) {
//获取访问权限并输出
int modifiers = field.getModifiers();
System.out.print(Modifier.toString(modifiers) + " ");
//输出变量的类型及变量名
System.out.println(field.getType().getName()
+ " " + field.getName());
}
}
/**
* 通过反射获取类的所有方法
*/
@Test
public void printMethods(){
//1.获取并输出类的名称
Class mClass = SonClass.class;
System.out.println("类的名称:" + mClass.getName());
//2.1 获取所有 public 访问权限的方法
//包括自己声明和从父类继承的
//Method[] mMethods = mClass.getMethods();
//2.2 获取所有本类的的方法(不问访问权限)
Method[] mMethods = mClass.getDeclaredMethods();
//3.遍历所有方法
for (Method method :
mMethods) {
//获取并输出方法的访问权限(Modifiers:修饰符)
int modifiers = method.getModifiers();
System.out.print(Modifier.toString(modifiers) + " ");
//获取并输出方法的返回值类型
Class returnType = method.getReturnType();
System.out.print(returnType.getName() + " "
+ method.getName() + "( ");
//获取并输出方法的所有参数
Parameter[] parameters = method.getParameters();
for (Parameter parameter:
parameters) {
System.out.print(parameter.getType().getName()
+ " " + parameter.getName() + ",");
}
//获取并输出方法抛出的异常
Class[] exceptionTypes = method.getExceptionTypes();
if (exceptionTypes.length == 0){
System.out.println(" )");
}
else {
for (Class c : exceptionTypes) {
System.out.println(" ) throws "
+ c.getName());
}
}
}
}
/**
* 访问对象的私有方法
* 为简洁代码,在方法上抛出总的异常,实际开发别这样
*/
@Test
public void getPrivateMethod() throws Exception{
//1. 获取 Class 类实例
TestClass testClass = new TestClass();
Class mClass = testClass.getClass();
//2. 获取私有方法
//第一个参数为要获取的私有方法的名称
//第二个为要获取方法的参数的类型,参数为 Class...,没有参数就是null
//方法参数也可这么写 :new Class[]{String.class , int.class}
Method privateMethod =
mClass.getDeclaredMethod("privateMethod", String.class, int.class);
//3. 开始操作方法
if (privateMethod != null) {
//获取私有方法的访问权
//只是获取访问权,并不是修改实际权限
privateMethod.setAccessible(true);
//使用 invoke 反射调用私有方法
//privateMethod 是获取到的私有方法
//testClass 要操作的对象
//后面两个参数传实参
privateMethod.invoke(testClass, "Java Reflect ", 666);
}
}
/**
* 修改对象私有变量的值
* 为简洁代码,在方法上抛出总的异常
*/
@Test
public void modifyPrivateFiled() throws Exception {
//1. 获取 Class 类实例
TestClass testClass = new TestClass();
Class mClass = testClass.getClass();
//2. 获取私有变量
Field privateField = mClass.getDeclaredField("MSG");
//3. 操作私有变量
if (privateField != null) {
//获取私有变量的访问权
privateField.setAccessible(true);
//修改私有变量,并输出以测试
System.out.println("Before Modify:MSG = " + testClass.getMsg());
//调用 set(object , value) 修改变量的值
//privateField 是获取到的私有变量
//testClass 要操作的对象
//"Modified" 为要修改成的值
privateField.set(testClass, "Modified");
System.out.println("After Modify:MSG = " + testClass.getMsg());
}
}
/**
* 修改对象私有常量的值
* 为简洁代码,在方法上抛出总的异常,实际开发别这样
*/
@Test
public void modifyFinalFiled() throws Exception {
//1. 获取 Class 类实例
TestClass testClass = new TestClass();
Class mClass = testClass.getClass();
//2. 获取私有常量
Field finalField = mClass.getDeclaredField("FINAL_VALUE");
//3. 修改常量的值
if (finalField != null) {
//获取私有常量的访问权
finalField.setAccessible(true);
//调用 finalField 的 getter 方法
//输出 FINAL_VALUE 修改前的值
System.out.println("Before Modify:FINAL_VALUE = "
+ finalField.get(testClass));
//修改私有常量
finalField.set(testClass, "Modified");
//调用 finalField 的 getter 方法
//输出 FINAL_VALUE 修改后的值
System.out.println("After Modify:FINAL_VALUE = "
+ finalField.get(testClass));
//使用对象调用类的 getter 方法
//获取值并输出
System.out.println("Actually :FINAL_VALUE = "
+ testClass.getFinalValue());
}
}
}