修改Java中的final字段

发布于 2024-08-08 06:46:43 字数 1607 浏览 7 评论 0原文

让我们从一个简单的测试用例开始:

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());
  }
}

任何人都想猜测将打印什么作为输出(显示在底部,以免立即破坏惊喜)。

问题是:

  1. 为什么原始整数和包装整数的行为不同?
  2. 为什么反射访问与直接访问返回不同的结果?
  3. 最困扰我的一个问题 - 为什么 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:

  1. Why do primitive and wrapped integer behave differently?
  2. Why does reflective vs direct access return different results?
  3. The one that plagues me most - why does String behave like primitive int and not like Integer?

Results (java 1.5):

reflection: primitiveInt = 84
direct: primitiveInt = 42
reflection: wrappedInt = 84
direct: wrappedInt = 84
reflection: stringValue = 84
direct: stringValue = 42

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

有木有妳兜一样 2024-08-15 06:46:43

编译时常量是内联的(在 javac 编译时)。请参阅 JLS,特别是 15.28 定义了常量表达式,13.4.9 讨论了二进制兼容性或最终字段和常量。

如果将字段设置为非最终字段或分配非编译时间常量,则该值不会内联。例如:

private final String stringValue = null!=null?"": "42";

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:

private final String stringValue = null!=null?"": "42";
为你鎻心 2024-08-15 06:46:43

在我看来,这更糟糕:一位同事指出了以下有趣的事情:

@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());
}

通过这样做,您可以更改正在运行的整个 JVM 的行为。
(当然你可以只改变-127到127之间的值)

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)

酒解孤独 2024-08-15 06:46:43

Reflection 的 set(..) 方法可与 FieldAccessor 配合使用。

对于int,它获取一个UnsafeQualifiedIntegerFieldAccessorImpl,其超类将readOnly属性定义为true,仅当字段为bothstaticfinal

因此,首先回答一个未提出的问题 - 这就是为什么 final 毫无例外地被更改的原因。

UnsafeQualifiedFieldAccessor 的所有子类都使用 sun.misc.Unsafe 类来获取值。其中的方法都是native,但它们的名称是getVolatileInt(..)getInt(..) (getVolatileObject(. .)getObject(..) 分别)。上述访问器使用“易失性”版本。如果我们添加非易失性版本,会发生以下情况:(

System.out.println("reflection: non-volatile primitiveInt = "
     unsafe.getInt(test, (long) unsafe.fieldOffset(getField("primitiveInt"))));

其中 unsafe 通过反射实例化 - 否则不允许)
(我为 IntegerString 调用 getObject

这给出了一些有趣的结果:

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

此时我记得 javaspecialists.eu 上的一篇文章讨论相关问题。它引用 JSR-133

如果在字段声明中将最终字段初始化为编译时常量,则可能不会观察到最终字段的更改,因为该最终字段的使用在编译时会被编译时常量替换。

第 9 章讨论了这个问题中观察到的细节。

事实证明,这种行为并不那么意外,因为 final 字段的修改应该只在对象初始化后立即发生。

Reflection's set(..) method works with FieldAccessors.

For int it gets an UnsafeQualifiedIntegerFieldAccessorImpl, whose superclass defines the readOnly property to be true only if the field is both static and final

So to first answer the unasked question - here's why the final is changed without exception.

All subclasses of UnsafeQualifiedFieldAccessor use the sun.misc.Unsafe class 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:

System.out.println("reflection: non-volatile primitiveInt = "
     unsafe.getInt(test, (long) unsafe.fieldOffset(getField("primitiveInt"))));

(where unsafe is instantiated by reflection - it is not allowed otherwise)
(and I call getObject for Integer and 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.eu discussing an related matter. It quotes 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.

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.

怀里藏娇 2024-08-15 06:46:43

这不是一个答案,但它带来了另一个混乱点:

我想看看问题是否出在编译时评估上,或者反射是否实际上允许 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());
  }
}

这是我得到的输出(在 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

为什么直接调用 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 each changeField() call.

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)

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 ?

音盲 2024-08-15 06:46:43

有一个解决方法可以解决这个问题。如果您在 static {} 块中设置私有静态最终归档的值,它将起作用,因为它不会内联 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");

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:

private static final String MY_FIELD;

static {
    MY_FIELD = "SomeText"
}

...

Field field = VisitorId.class.getDeclaredField("MY_FIELD");

field.setAccessible(true);
field.set(field, "fakeText");
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文