lass Person {
public final String name = "Mike";
}
这里声明 name 为非静态的属性只是为了说明反射修改 final 属性无关乎静态不静态,静态只是表现在它是一个类属性,在一个类加载器空间只会有一份拷贝,仅此而已。
创建一个通用方法进行反射修改属性值
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public
static
void
modify
(
Object
object
,
String
fieldName
,
Object
newFieldValue
)
throws
Exception
{
Field
field
=
object
.
getClass
(
)
.
getDeclaredField
(
fieldName
)
;
Field
modifiersField
=
Field
.
class
.
getDeclaredField
(
"modifiers"
)
;
modifiersField
.
setAccessible
(
true
)
;
//Field 的 modifiers 是私有的
modifiersField
.
setInt
(
field
,
field
.
getModifiers
(
)
&
~
Modifier
.
FINAL
)
;
if
(
!
field
.
isAccessible
(
)
)
{
field
.
setAccessible
(
true
)
;
}
field
.
set
(
object
,
newFieldValue
)
;
}
|
调用 modify(...) 方法试图修改 person 的 name 属性
1
2
3
|
Person
person
=
new
Person
(
)
;
modify
(
person
,
"name"
,
"Michael"
)
;
System
.
out
.
println
(
person
.
name
)
;
//输出仍然是 Mike
|
为什么代码执行下来没问题,但输出又还是原来的值呢?但总是可以通过反射方式获取到修改后的新值。这就是 Java 编译器对 final 属型的内联优化,即编译时把该 final 的值直接放到了引用它的地方。即使是反射修改了该属性,但这种事后处理于事无补。
所以我在标题中所说如何有效的修改 final 属性值是指:反射修改了 final 属性值后能够在后续代码中使用修改后的值。
如果 Person 有个方法
public String getName() {
return name;
}
在前面代码反射修改了 name 属性后,getName() 方法也仍然是返回 "Mike"。
那么 Java 会对什么类型的 final 值进行内联编译呢?它们基本类型 byte, char, short, int, long, float, double, boolean; 再加上 Literal String 类型(直接双引号字符串)。只要是不被编译器内联优化的 final 属性都可以通过反射有效的进行修改(修改后能使用到新的值)
String 类型比较特殊, 如果把 Person 类 name 属性改成用 new String("Mike")
public final String name = new String("Mike");
就能够有效的使用上面的反射代码修改 name 的值,最后
modify(person, "name", "Michael");
System.out.println(person.name) //输出就是 Michael 了
正如基本类型有相应的包装类型,可以把 new String("Mike") 想像为 "Mike" 的包装类型。也确实是基本类型的 final 包装类型可以被反射有效的修改,new String("Mike") 也可以。
但我们要是在 IDE 中写成 new String("Mike"), 可能被告知 new String("")
这种格式是冗余的,IDE 又会把它改成 "Mike",重而反射行为变得不正确。
还有,如果 final 属性值是通过构造函数传入的能不能有效的被修改,当然可以,因为它也不会被编译器内联优化。
总之一句话:只要不会被编译器内联优化的 final 属性就可以通过反射有效的进行修改 -- 修改后代码中可使用到新的值
为加深印象,我们最后作一个较全面的测试
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
public
class
TestFinal
{
public
static
void
main
(
String
[
]
args
)
throws
Exception
{
Person
jordan
=
new
Person
(
"Chicago"
)
;
modify
(
jordan
,
"firstName"
,
"Michael"
)
;
System
.
out
.
println
(
jordan
.
firstName
)
;
//Mike
modify
(
jordan
,
"lastName"
,
"Michael"
)
;
System
.
out
.
println
(
jordan
.
lastName
)
;
//Michael
modify
(
jordan
,
"age"
,
51f
)
;
System
.
out
.
println
(
jordan
.
age
)
;
//50.5
modify
(
jordan
,
"height"
,
1.98f
)
;
System
.
out
.
println
(
jordan
.
height
)
;
//1.98
modify
(
jordan
,
"address"
,
new
Address
(
"ccc"
,
"ddd"
)
)
;
System
.
out
.
println
(
jordan
.
address
.
line1
)
;
//ccc
modify
(
jordan
,
"city"
,
"Miami"
)
;
System
.
out
.
println
(
jordan
.
city
)
;
//Miami
}
}
class
Person
{
public
final
String
firstName
=
"Mike"
;
public
final
String
lastName
=
new
String
(
"Jordan"
)
;
//可被有效修改
public
final
float
age
=
50.5f
;
public
final
Float
height
=
1.99f
;
//可被有效修改
public
final
Address
address
=
new
Address
(
"aaa"
,
"bbb"
)
;
//可被有效修改
public
final
String
city
;
//可被有效修改
public
Person
(
String
city
)
{
this
.
city
=
city
;
}
}
final
class
Address
{
public
final
String
line1
;
public
final
String
line2
;
public
Address
(
String
line1
,
String
line2
)
{
this
.
line1
=
line1
;
this
.
line2
=
line2
;
}
}
|
我以前也有一人误区: 就是总以为加了 final 关键值,该属性就会被编译器内联优化,就不能用反射有效的进行修改。通过今天的梳理终于清晰的明白了:final 属性,只要不是基本类型和字面 String,就可以正常使用反射修改它的值。以前曾为了想反射修改某个 final 值还特意把 final
关键字给去掉,完全不用这么做。所以对于基本类型和字面 String, final 只是预示着它的值不能被正常的代码修改。
因此我们在声明 log 时
private static Logger log = LoggerFactory.getLogger(TestFinal.class);
和
private static final Logger log = LoggerFactory.getLogger(TestFinal.class);
性能上不会有什么差异,也不会因为多一个 final 而在任何使用到了 log 的地方内联它。只要留意基本类型和字面 String 就行了。
https://unmi.cc/java-reflection-modify-final-field/, 来自 隔叶黄莺 Unmi Blog