एक जावा स्ट्रिंग वास्तव में अपरिवर्तनीय है?


399

हम सभी जानते हैं कि Stringजावा में अपरिवर्तनीय है, लेकिन निम्नलिखित कोड की जांच करें:

String s1 = "Hello World";  
String s2 = "Hello World";  
String s3 = s1.substring(6);  
System.out.println(s1); // Hello World  
System.out.println(s2); // Hello World  
System.out.println(s3); // World  

Field field = String.class.getDeclaredField("value");  
field.setAccessible(true);  
char[] value = (char[])field.get(s1);  
value[6] = 'J';  
value[7] = 'a';  
value[8] = 'v';  
value[9] = 'a';  
value[10] = '!';  

System.out.println(s1); // Hello Java!  
System.out.println(s2); // Hello Java!  
System.out.println(s3); // World  

यह कार्यक्रम इस तरह क्यों संचालित होता है? और क्यों s1और s2बदल रहा है का मूल्य है , लेकिन नहीं s3?


394
आप प्रतिबिंब के साथ सभी प्रकार के बेवकूफ चालें कर सकते हैं। लेकिन आप मूल रूप से "वारंटी शून्य को हटा रहे हैं" यदि आप इसे करते हैं तो क्लास पर स्टिकर हटा दिया जाता है।
cHao

16
@DarshanPatel परावर्तन को निष्क्रिय करने के लिए एक SecurityManager का उपयोग करें
सीन पैट्रिक फ्लॉयड

39
यदि आप वास्तव में उन चीजों के साथ गड़बड़ करना चाहते हैं जो आप इसे बना सकते हैं ताकि (Integer)1+(Integer)2=42कैश्ड ऑटोबॉक्सिंग के साथ गड़बड़ कर सकें ; (असंतुष्ट-बम-जावा संस्करण) ( thedailywtf.com/Articles/Disgruntled-Bomb-Java-Edition.aspx )
रिचर्ड झुनझुनी

15
आप इस जवाब से चकित हो सकते हैं, मैंने लगभग 5 साल पहले लिखा था stackoverflow.com/a/1232332/27423 - यह C # में अपरिवर्तनीय सूचियों के बारे में है, लेकिन यह मूल रूप से एक ही बात है: मैं अपने डेटा को संशोधित करने से उपयोगकर्ताओं को कैसे रोक सकता हूं? और जवाब है, आप नहीं कर सकते हैं; प्रतिबिंब यह बहुत आसान बनाता है। एक मुख्य धारा की भाषा, जिसमें यह समस्या नहीं है, जावास्क्रिप्ट है, क्योंकि इसमें एक प्रतिबिंब प्रणाली नहीं है, जो एक क्लोजर के अंदर स्थानीय चर तक पहुंच सकती है, इसलिए निजी वास्तव में निजी मतलब है (भले ही इसके लिए कोई कीवर्ड नहीं है!)
डैनियल ईयरविकेकर

49
क्या कोई भी प्रश्न को अंत तक पढ़ रहा है ?? सवाल यह है कि, कृपया मुझे दोहराएं: "यह प्रोग्राम इस तरह क्यों संचालित होता है? S1 और s2 का मान क्यों बदला गया है और s3 के लिए परिवर्तित नहीं किया गया है?" सवाल यह नहीं है कि एस 1 और एस 2 क्यों बदले गए हैं! प्रश्न IS: WH3 s3 क्यों नहीं बदला गया?
रोलैंड पिहलाकास

जवाबों:


403

String अपरिवर्तनीय है * लेकिन इसका केवल यही अर्थ है कि आप इसे अपने सार्वजनिक API का उपयोग करके नहीं बदल सकते।

आप यहां क्या कर रहे हैं, प्रतिबिंब का उपयोग करके सामान्य एपीआई को दरकिनार कर रहा है। उसी तरह, आप एनम के मूल्यों को बदल सकते हैं, इंटेगर ऑटोबॉक्सिंग आदि में उपयोग की जाने वाली लुकअप तालिका को बदल सकते हैं।

अब, कारण s1और s2परिवर्तन मूल्य, यह है कि वे दोनों एक ही प्रशिक्षु स्ट्रिंग को संदर्भित करते हैं। संकलक ऐसा करता है (जैसा कि अन्य उत्तरों द्वारा उल्लेख किया गया है)।

कारण s3है नहीं वास्तव में एक सा मेरे लिए आश्चर्य की बात थी, के रूप में मैंने सोचा कि यह साझा करेंगे valueसरणी ( यह जावा के पुराने संस्करण में किया था , जावा 7u6 से पहले)। हालांकि, के स्रोत कोड को Stringदेखते हुए , हम देख सकते हैं कि एक विकल्प के लिए valueवर्ण सरणी वास्तव में कॉपी (उपयोग Arrays.copyOfRange(..)) है। यही कारण है कि यह अपरिवर्तित है।

आप SecurityManagerइस तरह के काम करने के लिए दुर्भावनापूर्ण कोड से बचने के लिए एक स्थापित कर सकते हैं। लेकिन ध्यान रखें कि कुछ लाइब्रेरी इस तरह के रिफ्लेक्शन ट्रिक्स (आमतौर पर ORM टूल्स, AOP लाइब्रेरी आदि) का उपयोग करने पर निर्भर करती हैं।

*) मैंने शुरू में लिखा था कि Stringवास्तव में अपरिवर्तनीय नहीं हैं, बस "प्रभावी अपरिवर्तनीय"। यह वर्तमान कार्यान्वयन में भ्रामक हो सकता है String, जहां valueसरणी वास्तव में चिह्नित है private final। यह अभी भी ध्यान देने योग्य है, हालांकि, जावा में एक सरणी को अपरिवर्तनीय घोषित करने का कोई तरीका नहीं है, इसलिए उचित एक्सेस संशोधक के साथ, इसकी कक्षा के बाहर इसे उजागर नहीं करने के लिए भी ध्यान रखा जाना चाहिए।


जैसा कि यह विषय अत्यधिक लोकप्रिय लगता है, यहाँ कुछ ने आगे पढ़ने का सुझाव दिया है: हेंज काबुत्ज़ का प्रतिबिंब पागलपन जावाज़ोन 2009 से बात करता है, जो ओपी में बहुत सारे मुद्दों को शामिल करता है, साथ ही अन्य प्रतिबिंब ... अच्छी तरह से ... पागलपन।

यह कवर करता है कि यह कभी-कभी उपयोगी क्यों होता है। और क्यों, ज्यादातर समय, आपको इससे बचना चाहिए। :-)


7
दरअसल, Stringइंटर्निंग जेएलएस का हिस्सा है ( "एक स्ट्रिंग शाब्दिक हमेशा क्लास स्ट्रिंग के एक ही उदाहरण को संदर्भित करता है" )। लेकिन मैं मानता हूं, Stringवर्ग के कार्यान्वयन विवरणों पर भरोसा करना अच्छा नहीं है ।
haraldK

3
शायद यही वजह है कि substringमौजूदा सरणी के "अनुभाग" का उपयोग करने के बजाय प्रतियां, अन्यथा अगर मेरे पास एक बहुत बड़ा तार था sऔर tउसमें से बुलाया गया एक छोटा सा विकल्प निकाला गया था, और मैंने बाद में छोड़ दिया sलेकिन रखा गया t, तो विशाल सरणी को जीवित रखा जाएगा। (कचरा एकत्र नहीं)। तो शायद यह प्रत्येक स्ट्रिंग मान के लिए अपने संबद्ध सरणी के लिए अधिक स्वाभाविक है?
जेपी स्टिग नील्सन

10
एक स्ट्रिंग और इसके सबस्ट्रिंग के बीच सरणियों को साझा करना भी निहित है कि प्रत्येक String उदाहरण को संदर्भित सरणी और लंबाई में ऑफसेट को याद रखने के लिए चर ले जाना था। यह एक ओवरहेड है जिसे दिए गए तार की कुल संख्या और सामान्य स्ट्रिंग्स के बीच के विशिष्ट अनुपात को अनदेखा नहीं करना है। क्योंकि वे हर स्ट्रिंग आपरेशन उसे धीमा मतलब के लिए मूल्यांकन करने के लिए किया था कि हर बस के लाभ के लिए स्ट्रिंग आपरेशन एक आपरेशन, एक सस्ते-स्ट्रिंग।
होल्गर

2
@ होलगर - हां, मेरी समझ यह है कि हाल के जेवीएम में ऑफसेट फ़ील्ड को हटा दिया गया था। और जब यह मौजूद था तब भी इसका उपयोग नहीं किया गया था।
हॉट लिक्स

2
@ सुपरकैट: इससे कोई फर्क नहीं पड़ता है कि आपके पास मूल कोड है या नहीं, स्ट्रिंग्स के लिए अलग-अलग कार्यान्वयन और एक ही जेवीएम के भीतर होने या byte[]एएससीआईआई स्ट्रिंग्स के लिए स्ट्रिंग होने या char[]दूसरों के लिए इसका मतलब है कि प्रत्येक ऑपरेशन को यह जांचना होगा कि यह किस तरह का स्ट्रिंग है। संचालन। यह कोड को स्ट्रिंग्स का उपयोग करने के तरीकों में कोड में बाधा उत्पन्न करता है जो कॉलर के संदर्भ जानकारी का उपयोग करके आगे के अनुकूलन का पहला चरण है। यह एक बड़ा प्रभाव है।
होल्गर

93

जावा में, यदि दो स्ट्रिंग आदिम चर एक ही शाब्दिक के लिए आरम्भ किए जाते हैं, तो यह दोनों चर के लिए एक ही संदर्भ प्रदान करता है:

String Test1="Hello World";
String Test2="Hello World";
System.out.println(test1==test2); // true

प्रारंभ

यही कारण है कि तुलना सही है। तीसरी स्ट्रिंग का उपयोग करके बनाया गया है substring()जो एक ही ओर इंगित करने के बजाय एक नया स्ट्रिंग बनाता है।

उप स्ट्रिंग

जब आप परावर्तन का उपयोग करके एक तार को एक्सेस करते हैं, तो आपको वास्तविक पॉइंटर मिलता है:

Field field = String.class.getDeclaredField("value");
field.setAccessible(true);

तो इसको बदलने से एक पॉइंटर रखने वाले स्ट्रिंग को बदल जाएगा, लेकिन जैसा s3कि एक नए स्ट्रिंग के साथ बनाया गया है क्योंकि substring()यह नहीं बदलेगा।

परिवर्तन


यह केवल शाब्दिक के लिए काम करता है और एक संकलन-समय अनुकूलन है।
स्पेसप्रेज़

2
@ Zaphod42 सच नहीं है। आप internगैर-शाब्दिक स्ट्रिंग पर मैन्युअल रूप से कॉल कर सकते हैं और लाभ प्राप्त कर सकते हैं।
क्रिस हेस

ध्यान दें, हालांकि: आप internविवेकपूर्ण तरीके से उपयोग करना चाहते हैं । जब आप मिश्रण में प्रतिबिंब जोड़ते हैं तो सब कुछ इंटर्न करने से आपको बहुत फायदा नहीं होता है, और कुछ सिर खुजाने वाले क्षणों का स्रोत हो सकता है।
cHao

Test1और Test1असंगत हैं test1==test2और जावा नामकरण परंपराओं का पालन नहीं करते हैं।
c0der

50

आप स्ट्रिंग की अपरिहार्यता को दरकिनार करने के लिए प्रतिबिंब का उपयोग कर रहे हैं - यह "हमले" का एक रूप है।

ऐसे बहुत से उदाहरण हैं जिन्हें आप इस तरह बना सकते हैं (जैसे कि आप किसी Voidवस्तु को तुरंत भी बदल सकते हैं), लेकिन इसका मतलब यह नहीं है कि स्ट्रिंग "अपरिवर्तनीय" नहीं है।

ऐसे मामले हैं जहां इस प्रकार के कोड का उपयोग आपके लाभ के लिए किया जा सकता है और "अच्छा कोडिंग" हो सकता है, जैसे कि मेमोरी से पासवर्ड को जल्द से जल्द संभव समय पर (जीसी से पहले) साफ़ करना

सुरक्षा प्रबंधक के आधार पर, आप अपने कोड को निष्पादित करने में सक्षम नहीं हो सकते हैं।


30

आप स्ट्रिंग ऑब्जेक्ट के "कार्यान्वयन विवरण" तक पहुंचने के लिए प्रतिबिंब का उपयोग कर रहे हैं। अपरिवर्तनशीलता किसी वस्तु के सार्वजनिक इंटरफ़ेस की विशेषता है।


24

दृश्यता संशोधक और अंतिम (यानी अपरिवर्तनीयता) जावा में दुर्भावनापूर्ण कोड के खिलाफ माप नहीं है; वे केवल गलतियों से बचाने और कोड को अधिक बनाए रखने के लिए उपकरण हैं (सिस्टम के बड़े विक्रय बिंदुओं में से एक)। यही कारण है कि आप Stringप्रतिबिंब के माध्यम से बैकिंग चार सरणी जैसे आंतरिक कार्यान्वयन विवरण तक पहुंच सकते हैं ।

दूसरा प्रभाव जो आप देखते हैं वह यह है कि सभी Stringपरिवर्तन होते हैं जबकि ऐसा लगता है कि आप केवल परिवर्तन करते हैं s1। यह जावा स्ट्रिंग शाब्दिक की एक निश्चित संपत्ति है कि वे स्वचालित रूप से इंटर्न किए गए हैं, अर्थात कैश किए गए हैं। एक ही मूल्य के साथ दो स्ट्रिंग शाब्दिक वास्तव में एक ही वस्तु होगी। जब आप newइसके साथ एक स्ट्रिंग बनाते हैं, तो इसे स्वचालित रूप से इंटर्न नहीं किया जाएगा और आपको यह प्रभाव दिखाई नहीं देगा।

#substringकुछ समय पहले तक (जावा 7u6) एक समान तरीके से काम करता था, जो आपके प्रश्न के मूल संस्करण में व्यवहार की व्याख्या करता था। इसने एक नया समर्थन चार्ट नहीं बनाया, लेकिन मूल स्ट्रिंग से एक को पुन: उपयोग किया; इसने केवल एक नई स्ट्रिंग ऑब्जेक्ट बनाया है जो केवल उस सरणी के एक हिस्से को प्रस्तुत करने के लिए एक ऑफसेट और एक लंबाई का उपयोग करता है। यह आम तौर पर स्ट्रिंग्स के रूप में काम करता है अपरिवर्तनीय हैं - जब तक कि आप उस पर ध्यान नहीं देते। इस संपत्ति का #substringयह भी मतलब था कि जब एक छोटा सबस्ट्रिंग बनाया गया था तब भी पूरे मूल स्ट्रिंग को इकट्ठा नहीं किया जा सकता था।

वर्तमान जावा और प्रश्न के आपके वर्तमान संस्करण के रूप में कोई अजीब व्यवहार नहीं है #substring


2
वास्तव में, दृश्यता संशोधक हैं (या कम से कम थे) सुरक्षा againts दुर्भावनापूर्ण कोड के रूप में इरादा - हालांकि, आप एक SecurityManager (System.setSecurityManager ()) सुरक्षा को सक्रिय करने के सेट करना होगा। यह वास्तव में कितना सुरक्षित है, यह एक और सवाल है ...
sleske

2
एक upvote का वर्णन करता है क्योंकि आप इस बात पर जोर देते हैं कि एक्सेस मॉडिफ़ायर का उद्देश्य 'सुरक्षा' कोड नहीं है। यह जावा और .NET दोनों में व्यापक रूप से गलत समझा जाता है। यद्यपि पिछली टिप्पणी इसके विपरीत है; मैं जावा के बारे में ज्यादा नहीं जानता, लेकिन .NET में यह निश्चित रूप से सच है। न तो भाषा में उपयोगकर्ताओं को यह मानना ​​चाहिए कि यह उनके कोड को हैक-प्रूफ बनाता है।
टॉम डब्ल्यू

finalप्रतिबिंब के माध्यम से भी अनुबंध का उल्लंघन करना संभव नहीं है । जावा 7u6 के बाद से भी, जैसा कि एक अन्य उत्तर में बताया गया है, #substringसरणियों को साझा नहीं करता है।
ntoskrnl

वास्तव में, समय के साथ व्यवहार finalमें बदलाव आया है ...: -ओ हेन्ज़ द्वारा "रिफ्लेक्शन मैडनेस" बात के अनुसार मैंने दूसरे धागे में पोस्ट किया, finalजिसका अर्थ था जेडीके 1.1, 1.3 और 1.4 में अंतिम, लेकिन 1.2 हमेशा का उपयोग करके प्रतिबिंब का उपयोग करके संशोधित किया जा सकता है। , और ज्यादातर मामलों में 1.5 और 6 में ...
haraldK

1
finalखेतों को nativeकोड के माध्यम से बदला जा सकता है, जैसा कि क्रमबद्ध उदाहरण के क्षेत्रों के साथ-साथ System.setOut(…)अंतिम System.outचर को संशोधित करते समय सीरियलाइजेशन फ्रेमवर्क द्वारा किया जाता है । उत्तरार्द्ध सबसे दिलचस्प विशेषता है क्योंकि एक्सेस ओवरराइड के साथ प्रतिबिंब static finalखेतों को नहीं बदल सकता है ।
Holger

11

स्ट्रिंग अपरिवर्तनीयता इंटरफ़ेस के दृष्टिकोण से है। आप इंटरफ़ेस को बायपास करने के लिए प्रतिबिंब का उपयोग कर रहे हैं और सीधे स्ट्रिंग उदाहरणों के आंतरिक को संशोधित करते हैं।

s1और s2दोनों को बदल दिया जाता है क्योंकि वे दोनों एक ही "इंटर्न" स्ट्रिंग उदाहरण के लिए असाइन किए गए हैं। आप स्ट्रिंग समानता और इंटर्निंग के बारे में इस लेख से उस भाग के बारे में थोड़ा और जान सकते हैं। आपको यह जानकर आश्चर्य हो सकता है कि आपके नमूना कोड में, s1 == s2रिटर्न true!


10

आप जावा के किस संस्करण का उपयोग कर रहे हैं? जावा 1.7.0_06 से, ओरेकल ने स्ट्रिंग के आंतरिक प्रतिनिधित्व को बदल दिया है, खासकर प्रतिस्थापन।

ओरेकल ट्यून्स जावा के आंतरिक स्ट्रिंग प्रतिनिधित्व से उद्धरण :

नए प्रतिमान में, स्ट्रिंग ऑफ़सेट और काउंट फ़ील्ड हटा दिए गए हैं, इसलिए सबस्ट्रिंग अब अंतर्निहित चार [] मूल्य को साझा नहीं करते हैं।

इस परिवर्तन के साथ, यह बिना प्रतिबिंब (???) के हो सकता है।


2
यदि ओपी एक पुराने सन / ओरेकल जेआरई का उपयोग कर रहा था, तो अंतिम विवरण "जावा!" (जैसा कि उसने गलती से पोस्ट किया था)। यह केवल स्ट्रिंग्स और सब स्ट्रिंग्स के बीच मूल्य सरणी के बंटवारे को प्रभावित करता है। आप अभी भी चाल के बिना मान को नहीं बदल सकते हैं, जैसे प्रतिबिंब।
haraldK

7

यहाँ वास्तव में दो प्रश्न हैं:

  1. क्या तार वास्तव में अपरिवर्तनीय हैं?
  2. S3 क्यों नहीं बदला गया?

बिंदु 1: रॉम को छोड़कर आपके कंप्यूटर में कोई अपरिवर्तनीय मेमोरी नहीं है। आजकल ROM भी कभी-कभी लिखने योग्य है। हमेशा कहीं न कहीं कुछ कोड होता है (चाहे वह कर्नेल या देशी कोड आपके प्रबंधित वातावरण को साइडस्टेपिंग कर रहा हो) जो आपके मेमोरी एड्रेस पर लिख सकता है। तो, "वास्तविकता" में, नहीं, वे बिल्कुल अपरिवर्तनीय नहीं हैं ।

2 को इंगित करने के लिए: इसका कारण यह है कि संभवतः स्ट्रिंग एक नया स्ट्रिंग उदाहरण आवंटित कर रहा है, जो संभवतः सरणी की प्रतिलिपि बना रहा है। इस तरह से प्रतिस्थापन को लागू करना संभव है कि यह प्रतिलिपि नहीं करेगा, लेकिन इसका मतलब यह नहीं है कि यह करता है। इसमें ट्रेडऑफ शामिल होते हैं।

उदाहरण के लिए, reallyLargeString.substring(reallyLargeString.length - 2)एक बड़ी मात्रा में स्मृति को जीवित रखने के लिए एक संदर्भ धारण करना चाहिए , या केवल कुछ बाइट्स?

यह निर्भर करता है कि प्रतिस्थापन कैसे लागू किया जाता है। एक गहरी प्रति कम स्मृति को जीवित रखेगी, लेकिन यह थोड़ा धीमा चलेगा। उथली प्रति अधिक स्मृति को जीवित रखेगी, लेकिन यह अधिक तेज़ होगी। एक गहरी प्रति का उपयोग करके ढेर के विखंडन को भी कम किया जा सकता है, क्योंकि स्ट्रिंग ऑब्जेक्ट और उसके बफर को एक ब्लॉक में आवंटित किया जा सकता है, जैसा कि 2 अलग-अलग ढेर आवंटन के विपरीत है।

किसी भी स्थिति में, ऐसा लगता है कि आपके जेवीएम ने कॉलिंग के लिए गहरी प्रतियों का उपयोग करने के लिए चुना है।


3
रियल ROM प्लास्टिक में छपे फोटोग्राफिक प्रिंट की तरह ही अपरिवर्तनीय है। पैटर्न स्थायी रूप से तब सेट होता है जब वेफर (या प्रिंट) रासायनिक रूप से विकसित होता है। रैम चिप्स सहित विद्युत-परिवर्तनशील यादें, "सही" रॉम के रूप में व्यवहार कर सकती हैं यदि इसे लिखने के लिए आवश्यक नियंत्रण संकेतों को सर्किट में अतिरिक्त विद्युत कनेक्शन जोड़ने के बिना सक्रिय नहीं किया जा सकता है, जिसमें यह स्थापित है। यह वास्तव में एम्बेडेड उपकरणों के लिए रैम को शामिल करने के लिए असामान्य नहीं है जो कारखाने में सेट किया गया है और एक बैक-अप बैटरी द्वारा बनाए रखा गया है, और जिनकी सामग्री फैक्ट्री द्वारा पुनः लोड किए जाने की आवश्यकता होगी यदि बैटटेइ विफल हो गई।
सुपरकैट

3
@ सुपर कंप्यूटर: आपका कंप्यूटर उन एम्बेडेड सिस्टमों में से एक नहीं है, हालाँकि। :) एक-दो दशक से पीसी में ट्रू हार्ड-वायर्ड रोम आम नहीं हैं; सब कुछ EEPROM और इन दिनों फ्लैश। मूल रूप से प्रत्येक उपयोगकर्ता-दृश्य पता जो मेमोरी को संदर्भित करता है, संभावित संभावित स्मृति को संदर्भित करता है।
cHao

@ कोको: कई फ्लैश चिप्स कुछ हिस्सों को एक फैशन में लिखने-संरक्षित करने की अनुमति देते हैं, जो कि अगर यह बिल्कुल भी पूर्ववत हो सकता है, तो सामान्य ऑपरेशन के लिए अलग-अलग वोल्टेज लागू करने की आवश्यकता होगी (जो कि मदरबोर्ड करने के लिए सुसज्जित नहीं होगा)। मुझे उम्मीद है कि मदरबोर्ड उस सुविधा का उपयोग करेंगे। इसके अलावा, मैं आज के कंप्यूटरों के बारे में निश्चित नहीं हूं, लेकिन ऐतिहासिक रूप से कुछ कंप्यूटरों में RAM का एक क्षेत्र होता है, जो बूट स्टेज के दौरान लिखा-संरक्षित होता था और इसे केवल एक रीसेट द्वारा असुरक्षित किया जा सकता था (जो ROM से शुरू होने के लिए निष्पादन को मजबूर करेगा)।
सुपरकैट

2
@supercat मुझे लगता है कि आप इस विषय को याद कर रहे हैं, जो यह है कि तार, रैम में संग्रहीत हैं, कभी भी वास्तव में अपरिवर्तनीय नहीं होने जा रहे हैं।
स्कॉट विस्न्यूस्की

5

@ HaraldK के उत्तर में जोड़ने के लिए - यह एक सुरक्षा हैक है जो ऐप में गंभीर प्रभाव पैदा कर सकता है।

पहली बात एक स्ट्रिंग पूल में संग्रहीत एक निरंतर स्ट्रिंग के लिए एक संशोधन है। जब स्ट्रिंग को एक के रूप में घोषित किया जाता है String s = "Hello World";, तो इसे और संभावित पुन: उपयोग के लिए एक विशेष ऑब्जेक्ट पूल में रखा जाता है। मुद्दा यह है कि कंपाइलर संकलित समय में संशोधित संस्करण के लिए एक संदर्भ रखेगा और उपयोगकर्ता द्वारा इस पूल में संग्रहीत स्ट्रिंग को रनटाइम पर संशोधित करने के बाद, कोड के सभी संदर्भ संशोधित संस्करण को इंगित करेंगे। यह निम्नलिखित बग में परिणाम देगा:

System.out.println("Hello World"); 

प्रिंट होगा:

Hello Java!

एक और मुद्दा था जिसका मैंने अनुभव किया जब मैं इस तरह के जोखिम भरे तार पर भारी गणना लागू कर रहा था। एक बग था जो गणना के दौरान 1000000 बार में से 1 की तरह हुआ जिसने परिणाम को अनिश्चित बना दिया। मैं जेआईटी को बंद करके समस्या का पता लगाने में सक्षम था - मुझे हमेशा जेआईटी के साथ एक ही परिणाम मिल रहा था। मेरा अनुमान है कि इसका कारण यह स्ट्रिंग सुरक्षा हैक था जिसने JIT अनुकूलन के कुछ अनुबंधों को तोड़ दिया था।


यह एक थ्रेड-सेफ्टी इश्यू हो सकता है, जो कि JIT के बिना धीमी निष्पादन समय और कम समसामयिकी द्वारा मास्क किया गया था।
टेड पेन्निंग्स

@TedPennings मेरे विवरण से, मैं अभी बहुत अधिक विवरण में नहीं जाना चाहता था। मैंने वास्तव में कुछ दिनों की तरह इसे स्थानीय बनाने की कोशिश की। यह एक एकल-थ्रेडेड एल्गोरिथ्म था जिसने दो अलग-अलग भाषाओं में लिखे गए दो ग्रंथों के बीच की दूरी की गणना की। मुझे इस मुद्दे के लिए दो संभावित सुधार मिले - एक था जेआईटी को बंद करना और दूसरा था String.format("")आंतरिक छोरों में से एक के अंदर शाब्दिक रूप से नो-ऑप जोड़ना । इसके लिए कुछ-अन्य-तब-जेआईटी-विफलता मुद्दा होने का एक मौका है, लेकिन मेरा मानना ​​है कि यह जेआईटी था, क्योंकि इस मुद्दे को फिर से जोड़ने के बाद फिर से पुन: पेश नहीं किया गया था।
एंड्री चशचेव

मैं JDK ~ 7u9 के शुरुआती संस्करण के साथ ऐसा कर रहा था, इसलिए यह हो सकता है।
एंड्री चशचेव

1
@Andrey Chaschev: "मुझे इस मुद्दे के लिए दो संभावित फ़िक्सेस मिले" ... तीसरा संभव फ़िक्स, जो Stringइंटर्नल में हैक नहीं करना है , आपके दिमाग में नहीं आया?
होल्गर

1
@ टेड पेनिंग: थ्रेड-सेफ्टी मुद्दे और JIT मुद्दे अक्सर बहुत समान होते हैं। JIT को कोड जेनरेट करने की अनुमति होती है जो finalऑब्जेक्ट निर्माण के बाद डेटा को संशोधित करते समय फ़ील्ड थ्रेड सुरक्षा गारंटी पर निर्भर करता है । इसलिए आप इसे जेआईटी मुद्दे या एमटी मुद्दे को अपनी इच्छानुसार देख सकते हैं। असली मुद्दा Stringडेटा को हैक करना और संशोधित करना है, जो अपरिवर्तनीय होने की उम्मीद है।
होल्गर

5

पूलिंग की अवधारणा के अनुसार, एक ही मान वाले सभी स्ट्रिंग वैरिएबल एक ही मेमोरी पते की ओर इशारा करेंगे। इसलिए s1 और s2, दोनों "हैलो वर्ल्ड" के समान मूल्य वाले हैं, एक ही मेमोरी लोकेशन (M1 कहते हैं) की ओर इशारा करेंगे।

दूसरी ओर, s3 में "विश्व" शामिल है, इसलिए यह एक अलग मेमोरी आवंटन (कहें M2) को इंगित करेगा।

तो अब क्या हो रहा है कि S1 के मूल्य को बदल दिया जा रहा है (चार [] मूल्य का उपयोग करके)। तो स्मृति स्थान M1 पर मान s1 और s2 द्वारा दोनों को बदल दिया गया है।

इसलिए, स्मृति स्थान M1 को संशोधित किया गया है जो s1 और s2 के मान में परिवर्तन का कारण बनता है।

लेकिन एम 2 का स्थान अनल्टेड रहता है, इसलिए s3 में समान मूल मूल्य होता है।


5

कारण s3 वास्तव में नहीं बदलता है क्योंकि जावा में है क्योंकि जब आप एक एस्ट्रिंग के लिए वैल्यू कैरेक्टर एरे को प्रतिस्थापित करते हैं तो आंतरिक रूप से कॉपी किया जाता है (Arrays.copyOfRange () का उपयोग करके)।

s1 और s2 समान हैं क्योंकि जावा में वे दोनों एक ही इंटर्न स्ट्रिंग को संदर्भित करते हैं। यह जावा में डिजाइन द्वारा है।


2
इससे पहले कि यह उत्तर आपके लिए कुछ भी जोड़ दे?
ग्रे

यह भी ध्यान दें कि यह एक काफी नया व्यवहार है, और किसी भी कल्पना से इसकी गारंटी नहीं है।
पाओलो एबरमैन

String.substring(int, int)जावा 7u6 के साथ परिवर्तन का कार्यान्वयन । 7u6 से पहले, JVM सिर्फ मूल करने के लिए एक सूचक रहेगा String'एस char[]एक सूचकांक और लंबाई के साथ एक साथ। 7u6 के बाद, यह एक नए में प्रतिस्थापन की प्रतिलिपि बनाता है Stringपेशेवरों और विपक्ष हैं।
एरिक जाब्लो

2

स्ट्रिंग अपरिवर्तनीय है, लेकिन प्रतिबिंब के माध्यम से आपको स्ट्रिंग वर्ग को बदलने की अनुमति है। आपने स्ट्रींग वर्ग को वास्तविक समय में परिवर्तनशील के रूप में पुनर्परिभाषित किया है। यदि आप चाहते हैं तो आप सार्वजनिक या निजी या स्थिर होने के लिए तरीकों को फिर से परिभाषित कर सकते हैं।


2
आप क्षेत्रों में से दृश्यता बदलते हैं / तरीकों यह नहीं उपयोगी है क्योंकि संकलन समय पर वे निजी हैं
बोहेमियन

1
आप तरीकों पर पहुंच बदल सकते हैं, लेकिन आप उनकी सार्वजनिक / निजी स्थिति को नहीं बदल सकते हैं और आप उन्हें स्थिर नहीं बना सकते।
ग्रे

1

[अस्वीकरण यह उत्तर का एक जानबूझकर विचारित शैली है क्योंकि मुझे लगता है कि अधिक "घर के बच्चों पर ऐसा न करें" उत्तर स्पष्ट है]

पाप वह रेखा है field.setAccessible(true);जो एक निजी क्षेत्र तक पहुंच की अनुमति देकर सार्वजनिक एपीआई का उल्लंघन करने के लिए कहती है। एक विशाल सुरक्षा छेद जिसे सुरक्षा प्रबंधक को कॉन्फ़िगर करके बंद किया जा सकता है।

प्रश्न में घटना कार्यान्वयन विवरण हैं जो आप कभी नहीं देखेंगे जब प्रतिबिंब के माध्यम से एक्सेस संशोधक का उल्लंघन करने के लिए कोड की उस खतरनाक रेखा का उपयोग नहीं किया जाएगा। स्पष्ट रूप से दो (सामान्य रूप से) अपरिवर्तनीय तार एक ही चार सरणी साझा कर सकते हैं। क्या कोई विकल्प एक ही सरणी साझा करता है, यह इस बात पर निर्भर करता है कि क्या यह हो सकता है और क्या डेवलपर इसे साझा करना चाहता है। आम तौर पर ये अदृश्य कार्यान्वयन विवरण होते हैं जो आपको तब तक नहीं जानना चाहिए जब तक कि आप कोड के उस लाइन के साथ सिर के माध्यम से एक्सेस संशोधक को शूट न करें।

यह ऐसे विवरणों पर भरोसा करने के लिए बस एक अच्छा विचार नहीं है, जो प्रतिबिंब का उपयोग करके एक्सेस संशोधक का उल्लंघन किए बिना अनुभव नहीं किया जा सकता है। उस वर्ग का स्वामी केवल सामान्य सार्वजनिक API का समर्थन करता है और भविष्य में कार्यान्वयन परिवर्तन करने के लिए स्वतंत्र है।

सभी ने कहा कि कोड की लाइन वास्तव में बहुत उपयोगी है जब आपके पास एक बंदूक होती है जो आपके सिर को पकड़ती है ताकि आप इस तरह के खतरनाक काम कर सकें। उस बैक डोर का उपयोग करना आमतौर पर एक कोड गंध है जिसे आपको बेहतर लाइब्रेरी कोड में अपग्रेड करने की आवश्यकता है जहां आपको पाप नहीं करना है। कोड की उस खतरनाक रेखा का एक और आम उपयोग "वूडू फ्रेमवर्क" (ऑरम, इंजेक्शन कंटेनर, ...) लिखना है। बहुत से लोग इस तरह के ढांचे (उनके लिए और उनके खिलाफ दोनों) के बारे में धार्मिक हो जाते हैं, इसलिए मैं यह कहकर लौ युद्ध को आमंत्रित करने से बचूंगा कि विशाल बहुमत के अलावा अन्य प्रोग्रामर को वहां जाने की जरूरत नहीं है।


1

जेवीएम हीप मेमोरी के स्थायी क्षेत्र में तार बनाए जाते हैं। तो हाँ, यह वास्तव में अपरिवर्तनीय है और बनने के बाद इसे बदला नहीं जा सकता है। क्योंकि जेवीएम में, तीन प्रकार की ढेर मेमोरी होती है: 1. युवा पीढ़ी 2. पुरानी पीढ़ी 3. स्थायी पीढ़ी।

जब कोई वस्तु बनाई जाती है, तो यह युवा पीढ़ी के ढेर क्षेत्र और पर्मगेन क्षेत्र में स्ट्रिंग पूलिंग के लिए आरक्षित हो जाती है।

यहाँ अधिक विस्तार से आप जा सकते हैं और इससे अधिक जानकारी प्राप्त कर सकते हैं: जावा में कचरा संग्रहण कैसे काम करता है


0

स्ट्रिंग प्रकृति में अपरिवर्तनीय है क्योंकि स्ट्रिंग ऑब्जेक्ट को संशोधित करने की कोई विधि नहीं है। यही कारण है कि उन्होंने StringBuilder और StringBuffer कक्षाएं शुरू कीं

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.