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

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

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

1
2
3
4
5
6
7
public class Test {
private final String NAME = "亦袁非猿";

public String getName() {
return NAME;
}
}

然后通过反射修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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如下:

1
2
3
4
5
6
7
8
public class Test {
private final String NAME = "亦袁非猿";

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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被赋值后,还是可以通过反射重新赋值的。包装类也是,如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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文件,看看被编译后的内容,没有被优化替换掉

1
2
3
4
5
6
7
8
9
10
11
12
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,直接上代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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的引用。

1
2
3
4
5
6
7
8
9
10
public class Test {
private final String LOCATION = "广州";

public Test() {
}

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

总结

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

公众号:亦袁非猿

欢迎关注,交流学习