2015-09-17 6 views
82

जावा स्ट्रिंग पूल प्रतिबिंब के साथ मिलकर जावा में कुछ अकल्पनीय परिणाम उपज कर सकते हैं:यह जावा कोड स्निपेट कैसे काम करता है? (स्ट्रिंग पूल और प्रतिबिंब)

import java.lang.reflect.Field; 

class MessingWithString { 
    public static void main (String[] args) { 
     String str = "Mario"; 
     toLuigi(str); 
     System.out.println(str + " " + "Mario"); 
    } 

    public static void toLuigi(String original) { 
     try { 
      Field stringValue = String.class.getDeclaredField("value"); 
      stringValue.setAccessible(true); 
      stringValue.set(original, "Luigi".toCharArray()); 
     } catch (Exception ex) { 
      // Ignore exceptions 
     } 
    } 
} 

कोड से ऊपर प्रिंट होगा:

"Luigi Luigi" 

मारियो का क्या हुआ?

+7

@ जो मैं कहूंगा कि इसे पास कर दें। [जेफ एटवुड: "मैंने चिंता करना बंद कर दिया है और प्यार (कुछ) नकल करना बंद कर दिया है। और आपको भी चाहिए।"] (Https://blog.stackoverflow.com/2010/11/dr-strangedupe-or-how-i- सीखा-से-रोक-चिंता-और-प्यार-डुप्लिकेशंस /) – Mindwin

+3

@ माइंडविन: इसका मतलब यह नहीं है कि अगर हम वास्तव में ऐसा करते हैं तो हमें डुप्लिकेट के रूप में प्रश्न बंद करना बंद करना चाहिए। असल में, जेफ का आलेख * हमें * डुप्लीकेट के रूप में प्रश्न बंद करने के लिए प्रोत्साहित करता है - क्योंकि यह उनसे जुड़ने का तरीका है। –

उत्तर

94

मारियो के साथ क्या हुआ ??

आपने इसे मूल रूप से बदल दिया। हां, प्रतिबिंब के साथ आप तारों की अपरिवर्तनीयता का उल्लंघन कर सकते हैं ... और स्ट्रिंग इंटर्निंग के कारण, इसका मतलब है कि "मारियो" का उपयोग (एक बड़ी स्ट्रिंग निरंतर अभिव्यक्ति के अलावा, जिसे संकलन-समय पर हल किया गया होगा) समाप्त हो जाएगा कार्यक्रम के बाकी हिस्सों में "लुइगी" के रूप में।

बात के इस प्रकार के कारण है कि प्रतिबिंब सुरक्षा अनुमति की आवश्यकता नहीं ...

ध्यान दें कि अभिव्यक्ति str + " " + "Mario"नहीं+ के बाएं संबद्धता के कारण, किसी भी संकलन समय संयोजन प्रदर्शन करता है। यह प्रभावी रूप से (str + " ") + "Mario" है, यही कारण है कि आप अभी भी Luigi Luigi देखते हैं। आप कोड को बदलते हैं तो:

System.out.println(str + (" " + "Mario")); 

... तो आप Luigi Mario देखने के रूप में संकलक "Mario" के लिए एक अलग स्ट्रिंग के लिए " Mario" प्रशिक्षु होगा करेंगे।

+0

"एक बड़ी स्ट्रिंग निरंतर अभिव्यक्ति के अलावा" बिट हर समय 100% सत्य नहीं हो सकता है। प्रश्न में, 'System.out।println' कॉल एक संकलन-समय निरंतर अभिव्यक्ति ('" "+" मारियो "') का उपयोग करता है, फिर भी "मारियो" का उदाहरण अभी भी समाप्त हो गया है। मुझे संदेह है कि यह एक अनुकूलन के कारण है जिसके द्वारा "मारियो" को प्रशिक्षित किया गया है और प्रत्यय मैच होने के कारण "मारियो" एक ही मेमोरी स्पेस को संदर्भित करता है, हालांकि मैंने इसकी पुष्टि नहीं की है। हालांकि, एक आम धारणा मामला है जो आम तौर पर सही कथन है। (या मैं सिर्फ गलत व्याख्या कर रहा हूं कि यह संकलन-समय स्थिर है या नहीं।) –

+7

@ChrisHayes: नहीं, यह '+' की साझेदारी के कारण संकलन-समय निरंतर अभिव्यक्ति नहीं है। इसका मूल्यांकन '(str +" ") +" मारियो "' के रूप में किया जाता है। यदि आप केवल '"+ मारियो' या' "" + "मारियो" + str' * प्रिंट करते हैं, तो * आपके पास संकलन-समय संगतता है, और आपको अभी भी आउटपुट में' मारियो 'मिलता है। –

+0

आह, मैं देखता हूं। यह समझ में आता है, अगर यह तुरंत सहज नहीं है। स्पष्टीकरण के लिए धन्यवाद। –

24

यह लुइगी पर सेट किया गया था। जावा में स्ट्रिंग्स अपरिवर्तनीय हैं; इस प्रकार, संकलक "Mario" के सभी उल्लेखों को उसी स्ट्रिंग निरंतर पूल आइटम (लगभग मोटे तौर पर, "स्मृति स्थान") के संदर्भ के रूप में समझा सकता है। आपने उस आइटम को बदलने के लिए प्रतिबिंब का उपयोग किया; इसलिए आपके कोड में सभी "Mario" अब हैं जैसे आपने "Luigi" लिखा था।

+1

* "... उसी स्मृति स्थान के संदर्भ के रूप में ..." * - कंपाइलर स्मृति स्थानों में सौदा नहीं करता है, और रनटाइम सिस्टम ऐसा नहीं करता क्योंकि किसी भी स्ट्रिंग का स्मृति स्थान किसी भी समय कचरा कलेक्टर द्वारा बदला जा सकता है। (मैं समझता हूं कि आप क्या कहने की कोशिश कर रहे हैं ... लेकिन आप इसे गलत तरीके से व्यक्त कर रहे हैं। अगर आप सी या सी ++ के बारे में बात कर रहे थे, तो यह स्पष्टीकरण लगभग सही है। जावा के लिए यह नहीं है।) –

+0

@StephenC: हालांकि यह होगा "स्ट्रिंग निरंतर पूल में एक ही इंडेक्स" कहने के लिए बेहतर रहा है, अंत में प्रभाव समान है: '" मारियो "' * * स्मृति स्थान में संग्रहीत है (क्योंकि यहां तक ​​कि JVM को अंततः अंतर्निहित वास्तुकला पर व्याख्या करने की आवश्यकता है, जहां इसे कहीं आवंटित किया जाएगा), और यदि जीसी इसे ले जाता है, तो यह अभी भी सच है कि 'मारियो" के सभी उल्लेख एक ही (स्थानांतरित) स्थान का संदर्भ लेंगे। फिर भी, आपके पास एक बिंदु है - मुझे जावा-उपयुक्त शब्दजाल का उपयोग करना चाहिए, इसलिए मैं इसे बदल दूंगा। – Amadan

+1

कहने का सबसे अच्छा तरीका यह कहना है कि वे सभी एक ही वस्तु हैं। और अंत में जावा रनटाइम सिस्टम है जो यह संकलक नहीं सुनिश्चित करता है। –

9

स्ट्रिंग पूल में स्ट्रिंग अक्षर संग्रहित होते हैं और उनके कैननिकल मान का उपयोग किया जाता है। "Mario" दोनों ही अक्षर एक ही मान के साथ स्ट्रिंग नहीं हैं, वे एक ही ऑब्जेक्ट हैं। उनमें से एक को छेड़छाड़ (प्रतिबिंब का उपयोग करके) उनमें से "दोनों" को संशोधित करेगा, क्योंकि वे एक ही वस्तु के केवल दो संदर्भ हैं।

16

मौजूदा उत्तरों को थोड़ा और समझाने के लिए, आइए आपके जेनरेट किए गए बाइट कोड (केवल main() विधि) पर नज़र डालें।

Byte Code

अब, सामग्री के उस स्थान के लिए कोई भी परिवर्तन दोनों संदर्भ को प्रभावित करेगा (और किसी अन्य आप भी दे)।

8

तुम बस Luigi जो कई द्वारा संदर्भित किया गया था String रों को स्ट्रिंग की String निरंतर पूलMario बदल गया है, इसलिए हर संदर्भित शाब्दिकMario अब Luigi है।

Field stringValue = String.class.getDeclaredField("value"); 

आप वर्ग String

stringValue.setAccessible(true); 

से char[] नामित value क्षेत्र दिलवाया है यह पहुंच योग्य बनाएं।

stringValue.set(original, "Luigi".toCharArray()); 

आप Luigi करने के लिए originalString क्षेत्र बदल दिया है। लेकिन मूल MarioStringशाब्दिक और शाब्दिक String पूल से संबंधित है और सभी इंटर्न हैं। जिसका अर्थ है कि सभी साहित्य जो समान सामग्री रखते हैं, उसी स्मृति पते को संदर्भित करते हैं।

String a = "Mario";//Created in String pool 
String b = "Mario";//Refers to the same Mario of String pool 
a == b//TRUE 
//You changed 'a' to Luigi and 'b' don't know that 
//'a' has been internally changed and 
//'b' still refers to the same address. 

मूल रूप से आप String पूल जो परिलक्षित सभी संदर्भित क्षेत्रों में मिला के मारियो बदल दिया है। यदि आप शाब्दिक के बजाय StringObject (यानी new String("Mario")) बनाते हैं तो आपको इस व्यवहार का सामना नहीं करना पड़ेगा क्योंकि आपके पास दो अलग-अलग Mario एस होंगे।

5

अन्य उत्तरों पर्याप्त रूप से बताते हैं कि क्या हो रहा है। मैं बस उस बिंदु को जोड़ना चाहता था कि यह केवल security manager स्थापित होने पर ही काम करता है। डिफ़ॉल्ट रूप से कमांड लाइन से कोड चलाते समय नहीं होता है, और आप इस तरह की चीजें कर सकते हैं। हालांकि एक ऐसे माहौल में जहां विश्वसनीय कोड अविश्वसनीय कोड के साथ मिश्रित होता है, जैसे किसी उत्पादन वातावरण में अनुप्रयोग सर्वर या ब्राउज़र में एक एप्लेट सैंडबॉक्स, आमतौर पर एक सुरक्षा प्रबंधक मौजूद होता है और आपको इन प्रकार के शेंगेनियों की अनुमति नहीं दी जाएगी, इसलिए ऐसा लगता है कि यह एक भयानक सुरक्षा छेद से कम है।

3

एक और संबंधित बिंदु: आप String.intern() विधि का उपयोग करके कुछ परिस्थितियों में स्ट्रिंग तुलना के प्रदर्शन को बेहतर बनाने के लिए निरंतर पूल का उपयोग कर सकते हैं।

वह विधि स्ट्रिंग के उदाहरण को स्ट्रिंग के रूप में उसी स्ट्रिंग के साथ देता है जिस पर स्ट्रिंग स्थिरांक पूल से इसे बुलाया जाता है, इसे जोड़कर यदि यह अभी तक मौजूद नहीं है। दूसरे शब्दों में, intern() का उपयोग करने के बाद, एक ही सामग्री वाले सभी स्ट्रिंग्स को एक दूसरे के रूप में एक ही स्ट्रिंग आवृत्ति और उन सामग्रियों के साथ स्ट्रिंग स्थिरांक के रूप में गारंटी दी जाती है, जिसका अर्थ है कि आप उन पर बराबर ऑपरेटर (==) का उपयोग कर सकते हैं।

यह एक उदाहरण मात्र है जो अपने दम पर बहुत उपयोगी नहीं है, लेकिन यह बिंदु दिखाता है:

class Key { 
    Key(String keyComponent) { 
     this.keyComponent = keyComponent.intern(); 
    } 

    public boolean equals(Object o) { 
     // String comparison using the equals operator allowed due to the 
     // intern() in the constructor, which guarantees that all values 
     // of keyComponent with the same content will refer to the same 
     // instance of String: 
     return (o instanceof Key) && (keyComponent == ((Key) o).keyComponent); 
    } 

    public int hashCode() { 
     return keyComponent.hashCode(); 
    } 

    boolean isSpecialCase() { 
     // String comparison using equals operator valid due to use of 
     // intern() in constructor, which guarantees that any keyComponent 
     // with the same contents as the SPECIAL_CASE constant will 
     // refer to the same instance of String: 
     return keyComponent == SPECIAL_CASE; 
    } 

    private final String keyComponent; 

    private static final String SPECIAL_CASE = "SpecialCase"; 
} 

इस छोटे चाल के चारों ओर अपने कोड को डिजाइन करने के लायक नहीं है, लेकिन यह ध्यान में रखने योग्य है उस दिन के लिए जब आप intern() के उचित उपयोग के साथ एक स्ट्रिंग पर == ऑपरेटर का उपयोग कर कुछ और अधिक गतिशील संवेदनशील कोड से बाहर निकल सकते हैं।

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