2017-08-14 51 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的情況下才會做這些事情,在其他情況下我可以通過反思「打破規則」 –