修改Java中的final字段
让我们从一个简单的测试用例开始:
import java.lang.reflect.Field;
public class Test {
private final int primitiveInt = 42;
private final Integer wrappedInt = 42;
private final String stringValue = "42";
public int getPrimitiveInt() { return this.primitiveInt; }
public int getWrappedInt() { return this.wrappedInt; }
public String getStringValue() { return this.stringValue; }
public void changeField(String name, Object value) throws IllegalAccessException, NoSuchFieldException {
Field field = Test.class.getDeclaredField(name);
field.setAccessible(true);
field.set(this, value);
System.out.println("reflection: " + name + " = " + field.get(this));
}
public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException {
Test test = new Test();
test.changeField("primitiveInt", 84);
System.out.println("direct: primitiveInt = " + test.getPrimitiveInt());
test.changeField("wrappedInt", 84);
System.out.println("direct: wrappedInt = " + test.getWrappedInt());
test.changeField("stringValue", "84");
System.out.println("direct: stringValue = " + test.getStringValue());
}
}
任何人都想猜测将打印什么作为输出(显示在底部,以免立即破坏惊喜)。
问题是:
- 为什么原始整数和包装整数的行为不同?
- 为什么反射访问与直接访问返回不同的结果?
- 最困扰我的一个问题 - 为什么 String 的行为像原始
int
而不是像Integer
?
结果(java 1.5):
reflection: primitiveInt = 84
direct: primitiveInt = 42
reflection: wrappedInt = 84
direct: wrappedInt = 84
reflection: stringValue = 84
direct: stringValue = 42
Let's start with a simple test case:
import java.lang.reflect.Field;
public class Test {
private final int primitiveInt = 42;
private final Integer wrappedInt = 42;
private final String stringValue = "42";
public int getPrimitiveInt() { return this.primitiveInt; }
public int getWrappedInt() { return this.wrappedInt; }
public String getStringValue() { return this.stringValue; }
public void changeField(String name, Object value) throws IllegalAccessException, NoSuchFieldException {
Field field = Test.class.getDeclaredField(name);
field.setAccessible(true);
field.set(this, value);
System.out.println("reflection: " + name + " = " + field.get(this));
}
public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException {
Test test = new Test();
test.changeField("primitiveInt", 84);
System.out.println("direct: primitiveInt = " + test.getPrimitiveInt());
test.changeField("wrappedInt", 84);
System.out.println("direct: wrappedInt = " + test.getWrappedInt());
test.changeField("stringValue", "84");
System.out.println("direct: stringValue = " + test.getStringValue());
}
}
Anybody care to guess what will be printed as output (shown at the bottom as to not spoil the surprise immediately).
The questions are:
- Why do primitive and wrapped integer behave differently?
- Why does reflective vs direct access return different results?
- The one that plagues me most - why does String behave like primitive
int
and not likeInteger
?
Results (java 1.5):
reflection: primitiveInt = 84
direct: primitiveInt = 42
reflection: wrappedInt = 84
direct: wrappedInt = 84
reflection: stringValue = 84
direct: stringValue = 42
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
编译时常量是内联的(在 javac 编译时)。请参阅 JLS,特别是 15.28 定义了常量表达式,13.4.9 讨论了二进制兼容性或最终字段和常量。
如果将字段设置为非最终字段或分配非编译时间常量,则该值不会内联。例如:
Compile-time constants are inlined (at javac compile-time). See the JLS, in particular 15.28 defines a constant expression and 13.4.9 discusses binary compatibility or final fields and constants.
If you make the field non-final or assign a non-compile time constant, the value is not inlined. For instance:
在我看来,这更糟糕:一位同事指出了以下有趣的事情:
通过这样做,您可以更改正在运行的整个 JVM 的行为。
(当然你可以只改变-127到127之间的值)
In my opinion this is even worse: A colleague pointed to the following funny thing:
By doing this, you can change the behaviour of the whole JVM you are running in.
(of course you can change only the values for the values between -127 and 127)
Reflection 的
set(..)
方法可与FieldAccessor
配合使用。对于
int
,它获取一个UnsafeQualifiedIntegerFieldAccessorImpl
,其超类将readOnly
属性定义为true,仅当字段为both时static
和final
因此,首先回答一个未提出的问题 - 这就是为什么
final
毫无例外地被更改的原因。UnsafeQualifiedFieldAccessor
的所有子类都使用sun.misc.Unsafe
类来获取值。其中的方法都是native
,但它们的名称是getVolatileInt(..)
和getInt(..)
(getVolatileObject(. .)
和getObject(..)
分别)。上述访问器使用“易失性”版本。如果我们添加非易失性版本,会发生以下情况:(其中
unsafe
通过反射实例化 - 否则不允许)(我为
Integer
和String
调用getObject
)这给出了一些有趣的结果:
此时我记得 javaspecialists.eu 上的一篇文章讨论相关问题。它引用 JSR-133:
第 9 章讨论了这个问题中观察到的细节。
事实证明,这种行为并不那么意外,因为
final
字段的修改应该只在对象初始化后立即发生。Reflection's
set(..)
method works withFieldAccessor
s.For
int
it gets anUnsafeQualifiedIntegerFieldAccessorImpl
, whose superclass defines thereadOnly
property to be true only if the field is bothstatic
andfinal
So to first answer the unasked question - here's why the
final
is changed without exception.All subclasses of
UnsafeQualifiedFieldAccessor
use thesun.misc.Unsafe
class to get the values. The methods there are allnative
, but their names aregetVolatileInt(..)
andgetInt(..)
(getVolatileObject(..)
andgetObject(..)
respectively). The aforementioned accessors use the "volatile" version. Here's what happens if we add the non-volatile version:(where
unsafe
is instantiated by reflection - it is not allowed otherwise)(and I call
getObject
forInteger
andString
)That gives some interesting results:
At this point I recall an article at javaspecialists.eu discussing an related matter. It quotes JSR-133:
Chapter 9 discusses the details observed in this question.
And it turns out this behaviour is not that unexpected, since modifications of
final
fields are supposed to happen only right after initialization of the object.这不是一个答案,但它带来了另一个混乱点:
我想看看问题是否出在编译时评估上,或者反射是否实际上允许 Java 绕过
final
关键字。这是一个测试程序。我添加的只是另一组 getter 调用,因此每个changeField()
调用之前和之后都有一个。这是我得到的输出(在 Eclipse,Java 1.6 下)
为什么直接调用 getWrappedInt() 会发生变化?
This is not an answer, but it brings up another point of confusion:
I wanted to see if the issue was compile-time evaluation or whether the reflection was actually allowing Java to get around the
final
keyword. Here's a test program. All I added was another set of getter calls, so there's one before and after eachchangeField()
call.Here's the output I get (under Eclipse, Java 1.6)
Why the heck does the direct call to getWrappedInt() change ?
有一个解决方法可以解决这个问题。如果您在 static {} 块中设置私有静态最终归档的值,它将起作用,因为它不会内联 fileld:
There is a work around for this. if you set the value of the private static final filed in the static {} block it will work because it will not inline the fileld: