अगर java.lang.String अंतिम नहीं होता तो वास्तव में क्या होता?


16

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

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


9
स्टैक ओवरफ्लो पर इस सवाल में आपकी रुचि हो सकती है: stackoverflow.com/questions/2068804/why-is-string-final-in-java
थॉमस ओवेन्स

इसी तरह, अगर ऐसा java.io.Fileनहीं होता तो क्या होता final। ओह, यह नहीं है। मैथुन।
टॉम हॉकिन -

1
पश्चिमी सभ्यता का अंत जैसा कि हम जानते हैं।
ट्यूलेंस कोर्डोवा

जवाबों:


22

मुख्य कारण गति है: finalकक्षाएं विस्तारित नहीं की जा सकती हैं, जो स्ट्रिंग्स को संभालने के दौरान जेआईटी को सभी प्रकार के अनुकूलन करने की अनुमति देती हैं - अतिप्रवाहित तरीकों की जांच करने की आवश्यकता कभी नहीं होती है।

एक और कारण थ्रेड सुरक्षा है: अपरिवर्तनीयता हमेशा थ्रेड सुरक्षित होती है क्योंकि किसी थ्रेड को पूरी तरह से बनाने से पहले उन्हें किसी और को पारित किया जा सकता है - और निर्माण के बाद, उन्हें अब और नहीं बदला जा सकता है।

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


2
यह गलत उत्तर है। जेवीएम कल्पना द्वारा आवश्यक सत्यापन के अलावा, हॉटस्पॉट केवल finalएक त्वरित जांच के रूप में उपयोग करता है कि एक विधि कोड का उपयोग करते समय ओवरराइड नहीं किया जाता है।
टॉम हॉकिन -

1
@ टॉमहॉटिन-टैक्लाइन: संदर्भ?
एरोन दिगुल्ला

1
आपको लगता है कि अपरिवर्तनीयता और अंतिम होना किसी भी तरह से संबंधित है। "एक और कारण है धागा सुरक्षा: अपरिवर्तनीयता हमेशा थ्रेड सेफ बी ..."। किसी वस्तु के अपरिवर्तनीय होने या न होने का कारण यह नहीं है कि वे अंतिम हैं या नहीं।
तुलसी कोर्डोवा

1
इसके अलावा, स्ट्रिंग वर्ग है की अपरिवर्तनीय पर ध्यान दिए बिना finalवर्ग पर कीवर्ड। चरित्र सरणी उसमें शामिल है यह अपरिवर्तनीय बनाने फाइनल। वर्ग बनाने के बाद ही अंतिम लूप बंद हो जाता है @ बार के रूप में उल्लेख किया गया है क्योंकि एक उत्परिवर्ती उपवर्ग को पेश नहीं किया जा सकता है।

1
@Snowman सटीक होना, वर्ण सरणी अंतिम होना केवल सरणी संदर्भ को अपरिवर्तनीय बनाता है, न कि सरणी की सामग्री।
माइकल कोस्मुलस्की

10

जावा के Stringवर्ग को अंतिम होने की एक और वजह है : यह कुछ परिदृश्यों में सुरक्षा के लिए महत्वपूर्ण है। यदि Stringवर्ग अंतिम नहीं था, तो निम्न कोड दुर्भावनापूर्ण कॉलर द्वारा सूक्ष्म हमलों के लिए असुरक्षित होगा:

 public String makeSafeLink(String url, String text) {
     if (!url.startsWith("http:") && !url.startsWith("https:"))
         throw SecurityException("only http/https URLs are allowed");
     return "<a href=\"" + escape(url) + "\">" + escape(text) + "</a>";
 }

हमला: एक दुर्भावनापूर्ण कॉलर स्ट्रिंग का एक उपवर्ग बना सकता है EvilString, जहां EvilString.startsWith()हमेशा सच होता है लेकिन जहां का मूल्य EvilStringकुछ बुराई है (जैसे, javascript:alert('xss'))। उपवर्ग के कारण, यह सुरक्षा जांच से बच जाएगा। इस हमले को समय-समय-समय-पर-उपयोग (TOCTTOU) भेद्यता के रूप में जाना जाता है: उस समय के बीच जब जाँच की जाती है (कि url http / https से शुरू होता है) और उस समय जब मूल्य का उपयोग किया जाता है (html स्निपेट के निर्माण के लिए), प्रभावी मूल्य बदल सकता है। यदि Stringअंतिम नहीं था, तो TOCTTOU जोखिम व्याप्त हो जाएगा।

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


बेशक, char[]स्ट्रिंग के अंदर को संशोधित करने के लिए प्रतिबिंब का उपयोग करके उस TOCTTOU को हटा देना अभी भी संभव है , लेकिन इसके लिए समय में कुछ भाग्य की आवश्यकता होती है।
पीटर टेलर

2
@ पेटर टेलर, यह उससे कहीं अधिक जटिल है। यदि सुरक्षा प्रबंधक आपको ऐसा करने की अनुमति देता है, तो आप केवल निजी चर का उपयोग करने के लिए प्रतिबिंब का उपयोग कर सकते हैं। Apple और अन्य अविश्वसनीय कोड को यह अनुमति नहीं दी गई है, और इस प्रकार char[]स्ट्रिंग के अंदर निजी को संशोधित करने के लिए प्रतिबिंब का उपयोग नहीं किया जा सकता है ; इसलिए, वे TOCTTOU भेद्यता का फायदा नहीं उठा सकते हैं।
डीडब्ल्यू

मुझे पता है, लेकिन वह अभी भी एप्लिकेशन और हस्ताक्षर किए गए एप्लेट छोड़ देता है - और कितने लोग वास्तव में एक स्व-हस्ताक्षरित ऐपलेट के लिए चेतावनी संवाद से डरते हैं?
पीटर टेलर

2
@ पेटर, वह बात नहीं है। मुद्दा यह है कि भरोसेमंद जावा पुस्तकालयों को लिखना बहुत अधिक कठिन होगा, और यदि Stringअंतिम नहीं था , तो जावा पुस्तकालयों में बहुत अधिक सूचित भेद्यताएं होंगी । जब तक कुछ समय के लिए यह खतरा मॉडल प्रासंगिक है, तब तक बचाव करना महत्वपूर्ण हो जाता है। जब तक यह एप्लेट्स और अन्य अविश्वसनीय कोड के लिए महत्वपूर्ण है String, तब तक यह अंतिम रूप से अंतिम रूप देने के लिए पर्याप्त है , भले ही इसके लिए विश्वसनीय अनुप्रयोगों की आवश्यकता न हो। (किसी भी मामले में, विश्वसनीय एप्लिकेशन पहले से ही सभी सुरक्षा उपायों को बायपास कर सकते हैं, भले ही यह अंतिम हो।)
डीडब्ल्यू

"हमला: एक दुर्भावनापूर्ण कॉलर स्ट्रिंग, एविलस्ट्रीमिंग का उपवर्ग बना सकता है, जहां EvilString.startsWith () हमेशा सही रिटर्न देता है ..." - नहीं तो शुरू होता है () अंतिम है।
एरिक

4

यदि नहीं, तो मल्टीथ्रेडेड जावा एप्स गड़बड़ होगी (वास्तव में जो है उससे भी बदतर)।

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


3
finalकक्षाओं का कक्षा की परिवर्तनशीलता से कोई लेना-देना नहीं है।

यह उत्तर प्रश्न को संबोधित नहीं करता है। -1
22

3
जर्रॉड रॉबर्सन: यदि वर्ग अंतिम नहीं था, तो आप विरासत का उपयोग करके परिवर्तनशील स्ट्रिंग्स बना सकते हैं।
ysdx

@JarrodRoberson अंत में किसी ने त्रुटि को नोटिस किया। स्वीकृत उत्तर का तात्पर्य भी अंतिम समान अपरिवर्तनीय है।
तुलसी कोर्डोवा

1

Stringअंतिम होने का एक और फायदा यह है कि यह अपरिवर्तनीयता की तरह ही पूर्वानुमानित परिणाम सुनिश्चित करता है। Stringहालांकि, एक वर्ग, कुछ परिदृश्यों में माना जाता है, जैसे एक मूल्य प्रकार (संकलक भी इसके माध्यम से समर्थन करता है String blah = "test")। इसलिए, यदि आप एक निश्चित मूल्य के साथ एक स्ट्रिंग बनाते हैं, तो आप यह अपेक्षा करते हैं कि आपके पास कुछ ऐसे स्ट्रिंग के लिए कुछ व्यवहार है, जो आपके द्वारा पूर्णांक के साथ की गई अपेक्षाओं के समान है।

इस बारे में सोचें: क्या होगा अगर आप उप-क्लास java.lang.Stringया ओवर-रोड equalsया hashCodeतरीकों को अपनाते हैं? अचानक दो स्ट्रिंग्स "परीक्षण" अब एक दूसरे के बराबर नहीं हो सकते।

अराजकता की कल्पना करो अगर मैं यह कर सकता था:

public class Password extends String {
    public Password(String text) {
        super(text);
    }

    public boolean equals(Object o) {
        return true;
    }
}

कुछ अन्य वर्ग:

public boolean isRightPassword(String suppliedString) {
    return suppliedString != null && suppliedString.equals("secret");
}

public void login(String username, String password) {
    return isRightUsername(username) && isRightPassword(password);
}

public void loginTest() {
    boolean success = login("testuser", new Password("wrongpassword"));
    // success is true, despite the password being wrong
}

1

पृष्ठभूमि

तीन स्थान हैं जहाँ जावा में फाइनल दिखाया जा सकता है:

कक्षा को अंतिम बनाना कक्षा के सभी उपवर्ग को रोकता है। एक विधि को अंतिम बनाना विधि के उपवर्गों को ओवरराइड करने से रोकता है। फ़ील्ड फ़ाइनल बनाना, इसे बाद में बदलने से रोकता है।

गलत धारणाएं

ऐसे अनुकूलन हैं जो अंतिम तरीकों और क्षेत्रों के आसपास होते हैं।

एक अंतिम विधि हॉटस्पॉट को इनलाइनिंग के माध्यम से अनुकूलित करना आसान बनाता है। हालाँकि, अगर यह अंतिम नहीं है, तो भी HotSpot ऐसा करता है, क्योंकि यह इस धारणा पर काम करता है कि इसे अन्यथा सिद्ध नहीं किया गया है। इसके बारे में अधिक एसओ पर

एक अंतिम चर को आक्रामक रूप से अनुकूलित किया जा सकता है, और इसके बारे में और अधिक जेएलएस अनुभाग 17.5.3 में पढ़ा जा सकता है ।

हालांकि, उस समझ से एक के साथ बारे में पता होना चाहिए कि न तो इन अनुकूलन के एक बनाने के बारे में कर रहे हैं वर्ग फाइनल। क्लास फ़ाइनल करके कोई प्रदर्शन हासिल नहीं होता है।

किसी वर्ग के अंतिम पहलू का अपरिवर्तनीयता से कोई लेना-देना नहीं है। किसी के पास एक अपरिवर्तनीय वर्ग (जैसे बिगइन्टेगर ) हो सकता है जो अंतिम नहीं है, या एक ऐसा वर्ग जो परस्पर और अंतिम (जैसे स्ट्रिंगबर्ल ) है। यदि कोई वर्ग अंतिम होना चाहिए, तो यह निर्णय डिजाइन का प्रश्न है।

अंतिम नक्शा

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

मैप्स

पहली बात यह है कि यदि आप स्ट्रिंग को उप-वर्ग कर सकते हैं तो क्या होगा, इस बात का एहसास है कि कोई व्यक्ति एक स्थिर स्ट्रिंग वर्ग का निर्माण कर सकता है जो अन्यथा स्ट्रिंग प्रतीत होगा। इससे हर जगह मैप गड़बड़ हो जाएगा।

इस काल्पनिक कोड पर विचार करें:

Map t = new TreeMap<String, Integer>();
Map h = new HashMap<String, Integer>();
MyString one = new MyString("one");
MyString two = new MyString("two");

t.put(one, 1); h.put(one, 1);
t.put(two, 2); h.put(two, 2);

one.prepend("z");

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

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

आपको पढ़ने में रुचि हो सकती है क्यों स्ट्रिंग जावा में अपरिवर्तनीय है? स्ट्रिंग्स की अपरिवर्तनीय प्रकृति के बारे में अधिक जानकारी के लिए।

नापाक तार

स्ट्रिंग्स के लिए कई विभिन्न नापाक विकल्प हैं। विचार करें कि क्या मैंने एक स्ट्रिंग बनाई थी जो हमेशा बराबर होने पर वापस लौटी थी ... और कहा कि पासवर्ड चेक में पारित किया है? या फिर ऐसा किया है कि MyString को असाइनमेंट स्ट्रिंग की एक प्रति कुछ ईमेल पते पर भेजेगा?

जब आप स्ट्रिंग को उप-करने की क्षमता रखते हैं तो ये बहुत वास्तविक संभावनाएं होती हैं।

Java.lang स्ट्रिंग ऑप्टिमाइज़ेशन

जबकि इससे पहले कि मैं उल्लेख किया है कि अंतिम तेजी से स्ट्रिंग नहीं करता है। हालांकि, स्ट्रिंग क्लास (और अन्य वर्ग java.lang) हर java.langसमय स्ट्रिंग के लिए सार्वजनिक एपीआई के माध्यम से जाने के बजाय अन्य वर्गों को इंटर्नल के साथ छेड़छाड़ करने की अनुमति देने के लिए खेतों और विधियों के पैकेज स्तर के संरक्षण का लगातार उपयोग करते हैं । की तरह कार्य getChars रेंज जाँच या बिना lastIndexOf कि StringBuffer द्वारा किया जाता है, या निर्माता है कि शेयरों अंतर्निहित सरणी (ध्यान दें कि thats एक जावा 6 बात यह है कि स्मृति समस्याओं के कारण बदल गया था)।

यदि किसी ने स्ट्रिंग का उपवर्ग बनाया है, तो यह उन अनुकूलन को साझा करने में सक्षम नहीं होगा (जब तक कि यह java.langभी हिस्सा नहीं था , लेकिन यह एक सील पैकेज है )।

विस्तार के लिए कुछ डिजाइन करना कठिन है

विस्तार योग्य होने के लिए कुछ डिजाइन करना कठिन है । इसका मतलब है कि आप अपने इंटर्न्स के कुछ हिस्सों को उजागर करने में सक्षम होने के लिए कुछ और करने के लिए मिल गए हैं।

एक विस्तार योग्य स्ट्रिंग अपनी मेमोरी लीक को ठीक नहीं कर सकता था । इसके कुछ हिस्सों को उपवर्गों के संपर्क में लाना होगा और उस कोड को बदलने का मतलब होगा कि उपवर्ग टूट जाएंगे।

जावा बैकवर्ड संगतता में खुद को ढालता है और विस्तार करने के लिए कोर कक्षाएं खोलकर, कुछ चीजों को ठीक करने की क्षमता खो देता है जबकि तीसरे पक्ष के उपवर्गों के साथ संगणना बनाए रखता है।

चेकस्टाइल का एक नियम है कि यह लागू होता है (जो कि आंतरिक कोड लिखते समय मुझे वास्तव में निराश करता है) जिसे "DesignForExtension" कहा जाता है, जो लागू करता है कि हर वर्ग या तो है:

  • सार
  • अंतिम
  • खाली क्रियान्वयन

जिसके लिए तर्कसंगत है:

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

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

डेवलपर

डेवलपर होने के अपने हिस्से। संभावना है कि प्रत्येक डेवलपर के लिए अपने स्वयं के संग्रह के साथ अपने स्वयं के स्ट्रिंग उपवर्ग पैदा करेगा विचार करें utils उस में। लेकिन अब इन उपवर्गों को स्वतंत्र रूप से एक दूसरे को नहीं सौंपा जा सकता है।

WleaoString foo = new WleaoString("foo");
MichaelTString bar = foo; // This doesn't work.

इस तरह पागलपन की ओर जाता है। सभी जगह स्ट्रिंग करने के लिए कास्टिंग और यह देखने के लिए जाँच करें कि क्या स्ट्रिंग आपके स्ट्रिंग वर्ग का एक उदाहरण है या नहीं, तो उस एक के आधार पर एक नया स्ट्रिंग बनाना और ... बस, नहीं। मत करो।

मुझे यकीन है कि आप एक अच्छे स्ट्रिंग वर्ग लिख सकते हैं हूँ ... लेकिन उन पागल लोग हैं, जो सी लिखने ++ और से निपटने के लिए करने के लिए कई स्ट्रिंग कार्यान्वयन लिख छोड़ std::stringऔर char*और बढ़ावा और से कुछ SString , और सब आराम

जावा स्ट्रिंग जादू

कई जादुई चीजें हैं जो जावा स्ट्रिंग्स के साथ करती हैं। इससे प्रोग्रामर के लिए भाषा में कुछ असंगतियों का सामना करना आसान हो जाता है। स्ट्रिंग पर उपवर्गों को आवंटित करना इन जादुई चीजों से निपटने के बारे में कुछ बहुत महत्वपूर्ण विचार रखेगा:

  • स्ट्रिंग लिटरल्स (JLS 3.10.5 )

    कोड है कि एक करने की अनुमति देता है:

    String foo = "foo";

    यह पूर्णांक जैसे संख्यात्मक प्रकार के मुक्केबाजी के साथ भ्रमित नहीं होना है। आप नहीं 1.toString()कर सकते, लेकिन आप कर सकते हैं "foo".concat(bar)

  • +ऑपरेटर (JLS 15.18.1 )

    जावा में कोई अन्य संदर्भ प्रकार उस पर उपयोग किए जाने वाले ऑपरेटर के लिए अनुमति नहीं देता है। स्ट्रिंग विशेष है। स्ट्रिंग कॉन्सेटेशन ऑपरेटर कंपाइलर स्तर पर भी काम करता है, ताकि रनटाइम के बजाय यह संकलित "foo" + "bar"हो "foobar"जाए।

  • स्ट्रिंग रूपांतरण (JLS 5.1.11 )

    सभी वस्तुओं को स्ट्रिंग्स के संदर्भ में उपयोग करके सिर्फ स्ट्रिंग्स में परिवर्तित किया जा सकता है।

  • स्ट्रिंग इंटरस्टिंग ( JavaDoc )

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

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


0

यदि स्ट्रिंग अंतिम नहीं थी, तो प्रत्येक प्रोग्रामर टूल चेस्ट में "केवल कुछ अच्छे सहायक तरीकों के साथ स्ट्रिंग" होगा। इनमें से प्रत्येक अन्य सभी उपकरण चेस्ट के साथ असंगत होगा।

मैंने इसे एक मूर्खतापूर्ण प्रतिबंध माना। आज मुझे यकीन है कि यह एक बहुत ही अच्छा निर्णय था। यह स्ट्रिंग्स को स्ट्रिंग्स बनाये रखता है।


finalजावा finalमें C # के समान नहीं है । इस संदर्भ में, यह C # के समान ही है readonly। देखें stackoverflow.com/questions/1327544/…
आर्सेनी मूरज़ेंको

9
@ मेनमा: वास्तव में, इस संदर्भ में ऐसा लगता है कि यह सी # के समान है, जैसे कि सील है public final class String
विन्यासकर्ता
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.