我有这样的代码:
public class App {
private final String some;
public App(){
some = "old";
}
public static void main(String... args) throws NoSuchFieldException, IllegalAccessException {
App a = new App();
a.magic();
System.out.println(a.some);
}
private void magic() throws NoSuchFieldException, IllegalAccessException {
Field field = this.getClass().getDeclaredField("some");
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(this, "new");
String someDuplicate = (String) field.get(this);
System.out.println(someDuplicate);
}
}
输出结果为
new
new
但如果我将变量初始化更改为:
private final String some = "old";
输出将是
new
old
似乎内联初始化导致最终非静态字段的类静态行为
我找不到任何关于此行为的停靠引用,可能对此有一些合乎逻辑的解释。
顺便说一句,这种初始化字段的方式会导致类似于构造函数初始化情况的行为:
{
some = "old";
}
最佳答案
javac
执行常量内联。当您有诸如
class A {
final String text = "Hello";
public static void main(String... args) {
System.out.println(new A().text);
}
}
javac
可以内联编译时已知的常量。这使得更改基础字段对其内联的地方没有影响。
通过将值移动到构造函数,它在编译时不再已知。
转储 main
方法的字节码,您可以看到它不读取字段而是 LDC
加载常量 "Hello"
public static varargs main([Ljava/lang/String;)V
L0
LINENUMBER 5 L0
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
NEW A
DUP
INVOKESPECIAL A.<init> ()V
INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
POP
LDC "Hello"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L1
LINENUMBER 6 L1
RETURN
L2
LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
MAXSTACK = 3
MAXLOCALS = 1
我发现有趣的是,它仍然创建 A
并使用 .getClass()
检查它是否为 null,因此它的优化仅到此为止。
顺便说一句,您可以在不使用带有包装方法的构造函数/初始化 block 的情况下解决这个问题。
class A {
final String text = dynamic("Hello");
// or final String text = String.valueOf("Hello");
public static void main(String... args) {
System.out.println(new A().text);
}
static <T> T dynamic(T t) {
return t;
}
}
或者它无法在编译时确定的任何表达式。
关于java - final 以某种方式导致静态行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45673629/