2010-12-23 12 views
49
class WithPrivateFinalField { 
    private final String s = "I’m totally safe"; 
    public String toString() { 
     return "s = " + s; 
    } 
} 
WithPrivateFinalField pf = new WithPrivateFinalField(); 
System.out.println(pf); 
Field f = pf.getClass().getDeclaredField("s"); 
f.setAccessible(true); 
System.out.println("f.get(pf): " + f.get(pf)); 
f.set(pf, "No, you’re not!"); 
System.out.println(pf); 
System.out.println(f.get(pf)); 

आउटपुट:प्रतिबिंब के माध्यम से निजी अंतिम क्षेत्रों बदलने

s = I’m totally safe 
f.get(pf): I’m totally safe 
s = I’m totally safe 
No, you’re not! 

क्यों इसे इस तरह से काम करता है, तो आप कृपया समझा सकते हैं? पहला प्रिंट हमें बताता है कि निजी "एस" फ़ील्ड बदल नहीं गया है, जैसा कि मुझे उम्मीद है। लेकिन अगर हम प्रतिबिंब के माध्यम से क्षेत्र प्राप्त करते हैं, तो दूसरा प्रिंट शो, यह अपडेट किया गया है।

+0

इसे देखें: http: // stackoverflow।कॉम/प्रश्न/3301635/परिवर्तन-निजी-स्थैतिक-अंतिम-क्षेत्र-उपयोग-जावा-प्रतिबिंब/31268945 # 31268945 – iirekm

+0

http://stackoverflow.com/questions/3301635/change-private-static-final-field-using- जावा-प्रतिबिंब/31268945 # 31268945 – iirekm

उत्तर

68

This answer विषय पर संपूर्ण से अधिक है।

फिर भी अंतिम फील्ड्स से

JLS 17.5.3 बाद संशोधन, वहाँ जटिलताओं के एक नंबर रहे हैं। यदि अंतिम फ़ील्ड फ़ील्ड घोषणा में संकलन-समय निरंतर प्रारंभ होता है, तो अंतिम फ़ील्ड में परिवर्तनों को देखा नहीं जा सकता है, क्योंकि अंतिम फ़ील्ड का उपयोग संकलन समय स्थिरांक के साथ संकलित समय पर प्रतिस्थापित किया गया है।

लेकिन, यदि आप बहुत सावधानी से उपरोक्त अनुच्छेद पढ़ा है, तुम यहाँ के चारों ओर एक रास्ता खोज सकते हैं (निर्माता के बजाय क्षेत्र परिभाषा private final क्षेत्र सेट):

import java.lang.reflect.Field; 


public class Test { 

    public static void main(String[] args) throws Exception { 
    WithPrivateFinalField pf = new WithPrivateFinalField(); 
    System.out.println(pf); 
    Field f = pf.getClass().getDeclaredField("s"); 
    f.setAccessible(true); 
    System.out.println("f.get(pf): " + f.get(pf)); 
    f.set(pf, "No, you’re not!"); 
    System.out.println(pf); 
    System.out.println("f.get(pf): " + f.get(pf)); 
    } 

    private class WithPrivateFinalField { 
    private final String s; 

    public WithPrivateFinalField() { 
     this.s = "I’m totally safe"; 
    } 
    public String toString() { 
     return "s = " + s; 
    } 
    } 

} 

उत्पादन होता है फिर निम्नानुसार:

s = I’m totally safe 
f.get(pf): I’m totally safe 
s = No, you’re not! 
f.get(pf): No, you’re not! 

आशा है कि इससे थोड़ा सा मदद मिलेगी।

+1

+1 - यह वास्तविक जवाब है। ओपी का कार्यक्रम जेआईटी कंपाइलर की धारणा का कारण बन रहा है कि 's' अमान्य नहीं है। यह एक कंपाइलर बग नहीं है, क्योंकि जेएलएस स्पष्ट रूप से चेतावनी देता है कि यदि आप सेट किए जाने के बाद 'अंतिम' चर को संशोधित करते हैं तो इस तरह की "बुरी चीजें" हो सकती हैं। –

+5

@ स्टीफन सी: वास्तव में व्यवहार के पास * जेआईटी * संकलन के साथ कुछ लेना देना नहीं है। यह पहले से ही बाइटकोड कंपाइलर है जो 's' को 'रिटर्न' एस =" + एस; 'में संकलित-समय स्थिर के साथ बदल देता है। यह दो वर्ग, 'ए' और 'बी' बनाकर प्रदर्शित किया जा सकता है ताकि' ए 'को' बी 'में निरंतर परिभाषित किया गया हो। अब, 'बी' में निरंतर बदलना और केवल' बी 'को पुन: सम्मिलित करना पुराने स्थिर को' ए' में रखेगा! चुस्त, लेकिन सच है। –

+0

@ जुनास - ठीक है ... लेकिन यह इस तरह के अनुकूलन करने के लिए जेआईटी कंपाइलर के लिए भी वैध होगा। –

1

final होने के नाते, संकलक ने मूल्य को बदलने की उम्मीद नहीं की, इसलिए संभवतः यह स्ट्रिंग को सीधे आपके toString विधि में हार्डकोड किया गया।

15

यह

class WithPrivateFinalField { 
    private final String s = "I’m totally safe"; 
    public String toString() { 
     return "s = " + s; 
    } 
} 

वास्तव में इस तरह संकलित:

class WithPrivateFinalField { 
    private final String s = "I’m totally safe"; 
    public String toString() { 
     return "s = I’m totally safe"; 
    } 
} 

है, संकलन समय स्थिरांक inlined मिलता है।this प्रश्न देखें। सबसे आसान तरीका है इनलाइनिंग से बचने के लिए String इस तरह की घोषणा करने के लिए है:

private final String s = "I’m totally safe".intern(); 

अन्य प्रकारों के लिए, एक छोटी सी विधि कॉल काम कर देता है:

private final int integerConstant = identity(42); 
private static int identity(int number) { 
    return number; 
} 
+1

मुझे वास्तव में यह स्पष्ट स्पष्टीकरण और 'इंटर्न()' दृष्टिकोण पसंद है, इससे पहले नहीं पता था ... धन्यवाद। –

+3

असल में, 'toString' का उपयोग करें क्योंकि यह स्ट्रिंग पूल में लुकअप से बचाता है। –

6

यहाँ WithPrivateFinalField वर्ग फ़ाइल की एक विघटित है (मैं डाल यह सादगी के लिए एक अलग वर्ग) में:

WithPrivateFinalField(); 
    0 aload_0 [this] 
    1 invokespecial java.lang.Object() [13] 
    4 aload_0 [this] 
    5 ldc <String "I’m totally safe"> [8] 
    7 putfield WithPrivateFinalField.s : java.lang.String [15] 
    10 return 
     Line numbers: 
     [pc: 0, line: 2] 
     [pc: 4, line: 3] 
     [pc: 10, line: 2] 
     Local variable table: 
     [pc: 0, pc: 11] local: this index: 0 type: WithPrivateFinalField 

    // Method descriptor #22()Ljava/lang/String; 
    // Stack: 1, Locals: 1 
    public java.lang.String toString(); 
    0 ldc <String "s = I’m totally safe"> [23] 
    2 areturn 
     Line numbers: 
     [pc: 0, line: 6] 
     Local variable table: 
     [pc: 0, pc: 3] local: this index: 0 type: WithPrivateFinalField 

toString() विधि में ध्यान दें, निरंतर पर पता 0 [0 ldc <String "s = I’m totally safe"> [23] इस्तेमाल किया ] संकलक दिखाता है कि संकलक पहले से ही स्ट्रिंग शाब्दिक "s = " और निजी अंतिम क्षेत्र " I’m totally safe" को पहले से ही समेकित करता है और इसे संग्रहीत करता है। इंस्टेंस वैरिएबल s परिवर्तनों के बावजूद ToString() विधि हमेशा "s = I’m totally safe" लौटाएगी।

संबंधित मुद्दे