2017-08-14 57 views
1

我有这样的代码:最终导致静态不知何故

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 

好像直列初始化导致最终的非静态字段

我could'n发现任何船坞参照这种行为的静态样的行为,可能会出现这种一些合乎逻辑的解释。

顺便说这种方式来初始化场引起的行为就像在构造函数初始化情况:

{ 
    some = "old"; 
} 
+0

顺便说一句,通过反射使字段非最终没有做任何事情。 –

+0

@PeterLawrey它使我能够更改最终字段值,如果它被初始化不内联。问题是关于为什么不同的初始化导致不同的行为 –

+0

这就是为什么它是一个评论,而不是一个答案,我可以改变最终字段而不做这个伎俩。 –

回答

5

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并检查它因为null使用.getClass()所以它的优化只能走到目前为止。

顺便说一句,你可以在不使用构造函数/初始化块和包装方法的情况下解决这个问题。

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

或它在编译时无法确定的任何表达式。

+0

我可以为此工作两次upvote吗?即使它看起来如此简单,我从来没有想过它......好戏! – AxelH

+0

@AxelH'javac'可以确定'1 + 2'是'3'。 ;)它也可以确定''Hel“+”lo“'是'”Hello“' –

+0

似乎是关于javac分析代码的能力,在两种情况下(inline init或constructor \ initializer block) final变量,我们以后不能改变它,所以它在所有情况下都可以看起来像字段“LDC”的值,但在这个例子中,我可以看到它只在内联init的情况下才会做这些事情,在其他情况下我可以通过反思“打破规则” –