修改 Java 中的最终字段
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1615163/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Modifying final fields in Java
提问by ChssPly76
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
intand not likeInteger?
- 为什么原始整数和包装整数的行为不同?
- 为什么反射访问和直接访问会返回不同的结果?
- 最让我烦恼的一个 - 为什么 String 表现得像原始
int而不像Integer?
Results (java 1.5):
结果(Java 1.5):
reflection: primitiveInt = 84
direct: primitiveInt = 42
reflection: wrappedInt = 84
direct: wrappedInt = 84
reflection: stringValue = 84
direct: stringValue = 42
采纳答案by Tom Hawtin - tackline
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.
编译时常量是内联的(在 javac 编译时)。参见 JLS,特别是 15.28 定义了一个常量表达式,13.4.9 讨论了二进制兼容性或最终字段和常量。
If you make the field non-final or assign a non-compile time constant, the value is not inlined. For instance:
如果将该字段设为非最终字段或分配非编译时间常量,则不会内联该值。例如:
private final String stringValue = null!=null?"": "42";
private final String stringValue = null!=null?"": "42";
回答by GandalfIX
In my opinion this is even worse: A colleague pointed to the following funny thing:
在我看来,这更糟:一位同事指出了以下有趣的事情:
@Test public void testInteger() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { ???
Field value = Integer.class.getDeclaredField("value"); ???
value.setAccessible(true); ???
Integer manipulatedInt = Integer.valueOf(7); ???
value.setInt(manipulatedInt, 666); ???
Integer testInt = Integer.valueOf(7); ???
System.out.println(testInt.toString());
}
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)
通过这样做,您可以更改您正在运行的整个 JVM 的行为。(当然,您只能更改 -127 和 127 之间的值)
回答by Bozho
Reflection's set(..)method works with FieldAccessors.
反射的set(..)方法适用于FieldAccessors。
For intit gets an UnsafeQualifiedIntegerFieldAccessorImpl, whose superclass defines the readOnlyproperty to be true only if the field is bothstaticand final
对于int它得到的UnsafeQualifiedIntegerFieldAccessorImpl,其超类定义readOnly只有在字段属性为True,都static和final
So to first answer the unasked question - here's why the finalis changed without exception.
所以首先回答未提出的问题 - 这就是为什么final无一例外地改变了。
All subclasses of UnsafeQualifiedFieldAccessoruse the sun.misc.Unsafeclass to get the values. The methods there are all native, but their names are getVolatileInt(..)and getInt(..)(getVolatileObject(..)and getObject(..)respectively). The aforementioned accessors use the "volatile" version. Here's what happens if we add the non-volatile version:
所有子类都UnsafeQualifiedFieldAccessor使用sun.misc.Unsafe该类来获取值。那里的方法都有native,但它们的名字分别是getVolatileInt(..)和getInt(..)(getVolatileObject(..)和getObject(..))。上述访问器使用“易失性”版本。如果我们添加非易失性版本,会发生以下情况:
System.out.println("reflection: non-volatile primitiveInt = "
unsafe.getInt(test, (long) unsafe.fieldOffset(getField("primitiveInt"))));
(where unsafeis instantiated by reflection - it is not allowed otherwise)
(and I call getObjectfor Integerand String)
(其中unsafe由反射实例化-它没有否则允许)(我打电话getObject为Integer和String)
That gives some interesting results:
这给出了一些有趣的结果:
reflection: primitiveInt = 84
direct: primitiveInt = 42
reflection: non-volatile primitiveInt = 84
reflection: wrappedInt = 84
direct: wrappedInt = 84
reflection: non-volatile wrappedInt = 84
reflection: stringValue = 84
direct: stringValue = 42
reflection: non-volatile stringValue = 84
At this point I recall an article at javaspecialists.eudiscussing an related matter. It quotes JSR-133:
在这一点上,我想起了 javaspecialists.eu 上讨论相关问题的一篇文章。它引用了JSR-133:
If a final field is initialized to a compile-time constant in the field declaration, changes to the final field may not be observed, since uses of that final field are replaced at compile time with the compile-time constant.
如果在字段声明中将 final 字段初始化为编译时常量,则可能不会观察到对 final 字段的更改,因为该 final 字段的使用在编译时被替换为编译时常量。
Chapter 9 discusses the details observed in this question.
第 9 章讨论了在这个问题中观察到的细节。
And it turns out this behaviour is not that unexpected, since modifications of finalfields are supposed to happen only right after initialization of the object.
事实证明,这种行为并不出人意料,因为final字段的修改应该只在对象初始化之后发生。
回答by Jason S
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 finalkeyword. Here's a test program. All I added was another set of getter calls, so there's one before and after each changeField()call.
我想看看问题是编译时评估还是反射实际上允许 Java 绕过final关键字。这是一个测试程序。我添加的只是另一组 getter 调用,因此在每次changeField()调用之前和之后都有一个。
package com.example.gotchas;
import java.lang.reflect.Field;
public class MostlyFinal {
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 = MostlyFinal.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 {
MostlyFinal test = new MostlyFinal();
System.out.println("direct: primitiveInt = " + test.getPrimitiveInt());
test.changeField("primitiveInt", 84);
System.out.println("direct: primitiveInt = " + test.getPrimitiveInt());
System.out.println();
System.out.println("direct: wrappedInt = " + test.getWrappedInt());
test.changeField("wrappedInt", 84);
System.out.println("direct: wrappedInt = " + test.getWrappedInt());
System.out.println();
System.out.println("direct: stringValue = " + test.getStringValue());
test.changeField("stringValue", "84");
System.out.println("direct: stringValue = " + test.getStringValue());
}
}
Here's the output I get (under Eclipse, Java 1.6)
这是我得到的输出(在 Eclipse、Java 1.6 下)
direct: primitiveInt = 42
reflection: primitiveInt = 84
direct: primitiveInt = 42
direct: wrappedInt = 42
reflection: wrappedInt = 84
direct: wrappedInt = 84
direct: stringValue = 42
reflection: stringValue = 84
direct: stringValue = 42
Why the heck does the direct call to getWrappedInt() change ?
为什么直接调用 getWrappedInt() 会发生变化?
回答by Joan P.S
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:
有一个解决方法。如果您在 static {} 块中设置私有静态 final 的值,它将起作用,因为它不会内联 fileld:
private static final String MY_FIELD;
static {
MY_FIELD = "SomeText"
}
...
Field field = VisitorId.class.getDeclaredField("MY_FIELD");
field.setAccessible(true);
field.set(field, "fakeText");

