修改Java中的最终场(Modifying final fields in Java)

2019-08-31 18:53发布

让我们先从一个简单的测试案例:

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. 为什么反射VS直接访问返回不同的结果?
  3. 大多数困扰我的一个-为什么字符串行为像原始的int和不喜欢的Integer

结果(java的1.5):

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

Answer 1:

编译时间常数内联(在的javac编译时)。 见JLS,特别是15.28定义了一个常量表达式和13.4.9讨论二进制兼容性或最后字段和常数。

如果使场非最终还是分配非编译时间常数,该值不内联。 例如:

!?私有最终字符串stringValue的= NULL = NULL “”: “42”;



Answer 2:

在我看来,这是更糟糕的:一位同事指着下面的有趣的事:

@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之间只改变值的值)



Answer 3:

反射的set(..)方法适用于FieldAccessor秒。

对于int它得到一个UnsafeQualifiedIntegerFieldAccessorImpl ,其超定义readOnly属性只有在现场要真实staticfinal

所以首先回答没有提出的问题-在这里就是为什么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由反射实例化-这是不允许的其他方式)(我打电话getObjectIntegerString

这给了一些有趣的结果:

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领域都应该对象的初始化之后唯一正确的事情发生。



Answer 4:

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

我想看看,如果这个问题是编译时评价或是否反映实际允许Java来绕过final关键字。 这是一个测试程序。 所有我添加了另一组吸气的电话,所以有一个之前和之后的每个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直接调用()改变?



Answer 5:

有一个变通此。 如果设置了私有静态最后在静态申请的价值{}块,将工作,因为它不会内联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");


文章来源: Modifying final fields in Java