• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Java:反射机制学习

武飞扬头像
Julian Q
帮助3

前言

今天在复习Java的反射机制,其实笔者第一遍学习Java的时候,那时候不是很理解反射机制有什么用,恰巧互联网关于Java反射机制的论调(包括《Java核心技术》),大体便是如果你仅仅是做程序开发,而不是工具类的开发,你大概只要略读一下,了解个大概就行了。
但是当笔者在学习框架ARouter、移动安全、Java底层相关知识的时候,笔者就深深感觉到了反射机制的强大,尽管很多时候反射机制的效率一直为人所诟病,但在帮助程序员向高阶迈进的时候,反射仍然不失为一项伟大的机制。

一、Java反射机制具体是什么

这里引用了一些总结对Java反射机制具体是什么给出描述

能够分析类能力的程序称为反射(reflective) ——《Java核心技术》

Java 反射机制在程序运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种 动态的获取信息 以及 动态调用对象的方法 的功能称为 java 的反射机制。反射机制很重要的一点就是“运行时”,其使得我们可以在程序运行时加载、探索以及使用编译期间完全未知的 .class 文件。换句话说,Java 程序可以加载一个运行时才得知名称的 .class 文件,然后获悉其完整构造,并生成其对象实体、或对其 fields(变量)设值、或调用其 methods(方法)。

划重点:动态、运行时,对于任意一个类或对象,都能知道或调用这个类和属性和方法

为什么Java要有反射机制,为什么像C 之类的语言不需要有反射机制

关于这个问题有许多博主给出过其他解读,笔者这里根据自己的理解进行整理

  • C 的设计哲学更加抽象,更加精简,在程序编写时绝不会浪费一丝更多的空间,java或者C#下的反射,会登记类型的一切信息,全部的字段信息,全部的方法信息,全部的接口信息等,不管猿猴是否需要,反正就全部都要备案。甚至,java或者c#,对于任何一个类,包括临时辅助类,压根就不可能需要反射,都会一个一个地备案。这种有备无患的态度,向来为c 所不齿。简单来说就是Java的使用更加大开大合,而C 则更加追求精简
  • C 是静态语言,而反射机制的存在使得Java成为了半动态语言
反射机制宏观上能让我们做些什么
  • 利用Java机制,在Java程序中可以动态地去确定服务所要调用的类,可以根据前方的业务场景结果动态地更改后续调用的类,如动态代理和注入机制,或是在运行时根据注解完成不同的业务(这一点反射和注解完全是两个极端,笔者理解注解完全是被动的,而反射完全是主动的)
  • 可以使代码更加灵活,降低耦合,同时提高代码的可扩展性,直观体现是使用反射书写的代码相比较根据类型扩展的代码更加简洁

相关博客
根据反射获取注解内容

二、通过反射获取运行时标识Class类

1.Class类是什么

在程序运行期间,Java运行时,系统始终会为所有对象维护一个运行时类型标识,标识这个运行对象所属的类——虚拟机利用运行时类型信息选择要执行的正确方法,大家所熟悉的多态机制便是在运行时,虚拟机动态去选择正确的方法进行执行。
而针对所有对象从属类,进行信息保存的类便是Class类。

在笔者看来,Class类更像是一个登记类,军地里士兵(对象)众多,但很多士兵从属一个兵种如弓箭手、步兵(类),而每种士兵都要去一本小本本上登记自己是哪种士兵,以及这个士兵有什么可以干啥——弓箭手有50支箭,可以射箭;而针对每种士兵对应的每个小本本,便是Class类

2.如何获取Class类

通过反射获取类有以下三种方法:

  • getClass()方法
  • Class.forName()方法
  • T.class;

代码演示如下:

package com.qjy.javabasicpractice.string.reflection;

import com.qjy.javabasicpractice.string.bean.Employee;
import com.qjy.javabasicpractice.string.bean.Manager;

import java.lang.reflect.InvocationTargetException;

public class ReflectionTest {
    public static void main(String[] args) {
        /**
         * 获取Class类的三种方式
         */
        Employee employee = new Employee("Julian",20, true, 2000.00);
        //getClass()方法
        Class aClass = employee.getClass();
        //Class.forName()方法
        String name = aClass.getName();
        Class bClass = null;
        try {
            bClass = Class.forName(name);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        //T.class;
        Class cClass=Employee.class;
    }
}
学新通

其中第二种方法Class.forName();输入的参数是想要获取到类的Class对应的包名 类名,如想要获取到位于com.example.res包下的util类的Class类,则第二种方法输入的参数为

字符串形式

com.example.res.util

三、获取到Class类能做些什么

1.获取所有类的信息

这里主要介绍两种方法

  • getFields()
  • getDeclaredFields()

其中 getFields() 获取到的是隐式调用的Class参数对应类的本类及父类的public变量;
getDeclaredFields() 获取到的是隐式调用的Class参数的对应类的所有变量,包含private、protected权限的变量,但不包含父类的变量

举例如下:
Employee.class

package com.qjy.javabasicpractice.string.bean;

public class Employee {
    public String Name;
    public int age;
    public boolean isMale;
    private double salary;

    public String getName() {
        return Name;
    }

    public void setName(String name) {
        Name = name;
    }

    public Employee(){
        this("Julian",20, true, 2000.00);
    }
    public Employee(String name, int age, boolean isMale, double salary) {
        Name = name;
        this.age = age;
        this.isMale = isMale;
        this.salary = salary;
    }

    public int getAge() {
        return age;
    }

    public Employee(int age, boolean isMale, double salary) {
        this.age = age;
        this.isMale = isMale;
        this.salary = salary;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public boolean isMale() {
        return isMale;
    }

    public void setMale(boolean male) {
        isMale = male;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}
学新通

Manager.class

package com.qjy.javabasicpractice.string.bean;

public class Manager extends Employee {
    private double bonus;

    public Manager(String name, int age, boolean isMale, double salary, double bonus) {
        super(name, age, isMale, salary);
        this.bonus = bonus;
    }

    public Manager() {
        this("ZhangSan",55,true,20000.00,2000);
    }

    public void ReportSalary(){
        double moneyAll=getSalary() bonus;
        System.out.println("这季度的总和是" moneyAll);
    }
}
学新通

ReflectionTest.class

package com.qjy.javabasicpractice.string.reflection;

import com.qjy.javabasicpractice.string.bean.Employee;
import com.qjy.javabasicpractice.string.bean.Manager;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;

public class ReflectionTest {
    public static void main(String[] args) {
        /**
         * 获取Class类的三种方式
         */
        Employee employee = new Employee("Julian",20, true, 2000.00);
        //getClass()方法
        Class aClass = employee.getClass();
        //Class.forName()方法
        String name = aClass.getName();
        Class bClass = null;
        try {
            bClass = Class.forName(name);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        //T.class;
        Class cClass=Employee.class;

        //获取Manager Class
        Class dClass=Manager.class;
        Object object1 = null;
        try {
            object1 = dClass.getConstructor().newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (object1 instanceof Manager) {
            System.out.println("That object is Manager");
        }
        printFieldsAll(dClass);
    }

    public static void printFieldsAll(Class mClass){
        System.out.println("This Class name is " 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());
        }
    }
}
学新通

输出结果

  • getFields()
    学新通
  • getDeclaredFields()
    学新通

2.获取所有类的方法

类比获取变量,获取类方法主要有以下两个函数:

  • getMethods()获取的是当前类和父类的public方法
  • getDeclaredMethods()获取的是当前类的方法,不问访问权限

代码如下:

public static void printMethodsAll(Class mClass){
        //1.获取并输出类的名称
        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());
                }
            }
        }
    }
学新通

3.访问并操作类的私有变量和方法

调用私有方法
  • 获取Class类
  • getMethod()
  • invoke()
    通过反射调用一个类的私有方法,这里提供一个调用的例子
package com.qjy.javabasicpractice.string.bean;

public class GetMethodTestClass {
    private String MSG = "GetMethodTestClass";

    private void privateMethod(String head , int tail){
        System.out.print(head   tail "is Reflection");
    }

    public String getMsg(){
        return MSG;
    }
}

private static void getPrivateMethod(Class mClass) throws Exception{
        //1. 获取私有方法
        //第一个参数为要获取的私有方法的名称
        //第二个为要获取方法的参数的类型,参数为 Class...,没有参数就是null
        //方法参数也可这么写 :new Class[]{String.class , int.class}
        Method privateMethod = mClass.getDeclaredMethod("privateMethod", String.class, int.class);

        //2. 开始操作方法
        if (privateMethod != null) {
            //获取私有方法的访问权
            //只是获取访问权,并不是修改实际权限
            privateMethod.setAccessible(true);

            //使用 invoke 反射调用私有方法
            //privateMethod 是获取到的私有方法
            //testClass 要操作的对象
            //后面两个参数传实参
            privateMethod.invoke(mClass.getConstructor().newInstance(), "Java Reflect ", 666);
        }
    }
学新通

需要注意的是Method对象invoke的第一个参数一定要是实例化的对象,我这里是通过调用构造器实例化出了对象,如果直接第一个参数填写mClass,则会报java.lang.IllegalArgumentException的异常。

访问和修改私有变量

如同上文,访问私有方法一样,访问和修改私有变量,无非就是两步曲

  • 拿到私有变量/方法
  • 对其进行设置/调用
private static void modifyPrivateFiled() throws Exception {
        //1. 获取 Class 类实例
        GetTestClass testClass = new GetTestClass();
        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());
        }
    }
学新通
修改私有常量

常量,在Java中通常是指final关键字修饰的一经赋值便不可再被修改的变量。

那么通过反射机制,我们可以针对已赋值的常量,进行修改吗;修改常量的值,能够在使用到常量的地方生效吗。

首先我们要补充和了解使用JVM虚拟机是如何去使用常量的

Java 虚拟机(JVM)在编译 .java 文件得到 .class 文件时,会优化我们的代码以提升效率。其中一个优化就是:JVM 在编译阶段会把引用常量的代码替换成具体的常量值。


编译前的 .java 文件:

//注意是 String  类型的值
private final String FINAL_VALUE = "hello";

if(FINAL_VALUE.equals("world")){
    //do something
}

复制代码编译后得到的 .class 文件:

private final String FINAL_VALUE = "hello";
//替换为"hello"
if("hello".equals("world")){
    //do something
}

从中我们可以看到:JVM在使用常量时,会对部分常量在使用到的地方对其直接进行优化,这里的部分常量是指:
对于 int 、long 、boolean 以及 String 这些基本类型 JVM 会优化,而对于 Integer 、Long 、Boolean 这种包装类型,或者其他诸如 Date 、Object 类型则不会被优化。

所以我们可以这么理解:
我们在程序运行时刻依然可以使用反射修改常量的值(后面会代码验证),但是 JVM 在编译阶段得到的 .class 文件已经将常量优化为具体的值,在运行阶段就直接使用具体的值了,所以即使修改了常量的值也已经毫无意义了。

举例代码如下:

//String 会被 JVM 优化
private final String FINAL_VALUE = "FINAL";

public String getFinalValue(){
    //剧透,会被优化为: return "FINAL" ,拭目以待吧
    return FINAL_VALUE;
}

/**
 * 修改对象私有常量的值
 * 为简洁代码,在方法上抛出总的异常,实际开发别这样
 */
private static 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());
    }
}

学新通

打印结果如下:

Before Modify:FINAL_VALUE = FINAL
After Modify:FINAL_VALUE = Modified
Actually :FINAL_VALUE = FINAL

如果要保证反射修改的常量值在运行过程中是有效的,可以尝试在常量初始阶段不对其进行赋值,利用构造函数对其进行复制。

因为在利用构造函数对其进行赋值时,常量在JVM虚拟机的优化下并不会直接指向具体的值,而是指向常量本身
如下:
学新通

四、反射优缺点

优点

  • 程序扩展性更高,耦合性更低,代码更简洁

缺点

  • 程序无法运行在完全安全的环境中,性能降低(根本原因在于编译器没有办法对程序进行优化,反射绕过了编译器的优化方式,如JIT、AOT编译技术)

总结

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhffkcea
系列文章
更多 icon
同类精品
更多 icon
继续加载