java高级特性之反射详解

刚开始学习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());
        }
    }


}

参考文章:
Java 反射由浅入深 | 进阶必备
java之Class类详解
深入理解Java反射

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值