美文网首页DevSupport
final属性值能被反射修改吗?

final属性值能被反射修改吗?

作者: 亦猿非猿 | 来源:发表于2018-12-07 08:14 被阅读100次

在刚哥的知识星球中,看到有网友询问反射给final修饰的字段设值,为啥设值会失败,之前也深入学习一下反射,对这个问题有点迷惑,于是学习起来并写demo实践一下。

先创建一个Test类,里面含有final修饰的变量

public class Test {
    private final String NAME = "亦袁非猿";
    
    public String getName() {
        return NAME;
    }
}

然后通过反射修改

public class Client {
    public static void main(String[] args) {
        Test test = new Test();
        Class mClass = test.getClass();
        // 获取NAME变量进行修改
        Field field = mClass.getDeclaredField("NAME");
        if (field != null) {
           field.setAccessible(true);
           System.out.println("modify before "+field.get(test));
           // 进行修改 
           field.set(test, "钢丝");
           System.out.println("modify after "+field.get(test));
           System.out.println("getName = "+test.getName());
         }
    }
}

// 输出:
modify before 亦袁非猿
modify after 钢丝
getName = 亦袁非猿

看上面的输出,修改后再反射获取修改的变量,是修改成功的,但是,调用方法获取,发现返回还是修改前的值,为啥呢?

这里要涉及到Java虚拟机,在Java类加载阶段的准备阶段,会对被final修饰的属性做优化。在编译期被优化后的Test.class如下:

public class Test {
    private final String NAME = "亦袁非猿";
    
    public String getName() {
        // 可以发现,在class中,NAME被直接优化变成"亦袁非猿"了
        return "亦袁非猿";
    }
}

所以,通过反射修改NAME的值,再调用该方法也修改无效。要注意一点,NAME是可以被反射修改且修改成功了。

上面说到,只有基本数据类型和String类型才会做优化导致修改无效。对于包装类或者对象类型,还是可以修改成功的,不信,看下面的代码

private final String JOB = new String("安卓程序员");

// 进行反射修改
Field jobField = mClass.getDeclaredField("JOB");
if (jobField != null) {
    jobField.setAccessible(true);
    System.out.println("modify before "+jobField.get(test));
    jobField.set(test, new String("大前端程序员"));
    System.out.println("modify after "+jobField.get(test));
    System.out.println("getJob = "+test.getJOB());
}

// 输出:
modify before 安卓程序员
modify after 大前端程序员
getJob = 大前端程序员

看到输出可以发现,final被赋值后,还是可以通过反射重新赋值的。包装类也是,如下代码

private final Integer AGE = new Integer(18);

// 进行反射修改
Field ageFiled = mClass.getDeclaredField("AGE");
if (ageFiled != null) {
    ageFiled.setAccessible(true);
    System.out.println("modify before "+ageFiled.get(test));
    ageFiled.set(test, new Integer(19));
    System.out.println("modify after "+ageFiled.get(test));
    System.out.println("getAge = "+test.getAge());
}

// 输出:
modify before 18
modify after 19
getAge = 19

继续看Test.class文件,看看被编译后的内容,没有被优化替换掉

public class Test {
    private final String JOB = new String("安卓程序员");
    private final Integer AGE = new Integer(18);
    
    public Integer getAge() {
        return this.AGE;
    }

    public String getJOB() {
        return this.JOB;
    }
}

另外,final类型定义后,不一定需要立马赋值,可以在构件函数进行初始化,那么,能否修改呢?said is null,直接上代码。

public class Test {
    private final String LOCATION;
    
    public Test() {
        LOCATION = "广州";
    }
    
    public String getLocation() {
        return LOCATION;
    }
}

// 通过反射修改
Field locationField = mClass.getDeclaredField("LOCATION");
if (field != null) {
    locationField.setAccessible(true);
    System.out.println("modify before "+locationField.get(test));
    locationField.set(test, "深圳");
    System.out.println("modify after "+locationField.get(test));
    System.out.println("getName = "+test.getLocation());
}

// 输出:
modify before 广州
modify after 深圳
getName = 深圳

同样还是修改有效的,继续看Test.class文件,发现被优化后,构造函数的值,被直接移动到final的定义中,方法返回的是LOCATION的引用。

public class Test {
    private final String LOCATION = "广州";

    public Test() {
    }

    public String getLocation() {
        return this.LOCATION;
    }
}

总结

回到一开始的问题,final属性值能否被修改呢?这个就要看final修饰变量的类型以及初始化的时机,通过看编译后的class文件就可以知道了。

相关文章

网友评论

    本文标题:final属性值能被反射修改吗?

    本文链接:https://www.haomeiwen.com/subject/sgyzcqtx.html