परिचय: "पास बाय रेफरेंस" बनाम "पास बाय वैल्यू" की शाश्वत लड़ाई
\n\nयदि आपने कभी Java में कोड लिखा है और किसी मेथड में एक array या ऑब्जेक्ट पास किया है, और फिर आश्चर्यचकित हुए हैं कि मूल डेटा क्यों बदल गया — तो आप अकेले नहीं हैं। यह प्रश्न शुरुआती (और केवल वे ही नहीं) को भाषा के आरंभ से ही परेशान करता रहा है। Stack Overflow पर इसने लाखों व्यूज प्राप्त किए हैं, और टिप्पणियों में बहस वर्षों से थमी नहीं है।
\n\nआज हम एक बार और हमेशा के लिए समझेंगे: Java — यह "pass-by-reference" है या "pass-by-value"? स्पॉयलर: उत्तर स्पष्ट है, लेकिन शैतान, हमेशा की तरह, विवरण में है। हम केवल एक शुष्क परिभाषा नहीं देंगे, बल्कि वास्तविक उदाहरणों के साथ दिखाएंगे कि Java में आर्गुमेंट्स का पास होना कैसे काम करता है, और कई लोग क्यों भ्रमित हो जाते हैं।
\n\nJava के निर्माता क्या कहते हैं?
\n\nआधिकारिक दस्तावेज़ीकरण और स्वयं जेम्स गोस्लिंग (Java के निर्माता) बहस के लिए कोई जगह नहीं छोड़ते: Java हमेशा आर्गुमेंट्स को वैल्यू द्वारा पास करता है (pass-by-value)। कोई अपवाद नहीं। न प्रिमिटिव के लिए, न ऑब्जेक्ट के लिए।
\n\nलेकिन फिर कोड ऑब्जेक्ट्स को क्यों बदलता है? क्यों List या array, जो मेथड में पास किया गया, बदल जाता है? आइए समझते हैं।
Pass-by-value: इसका वास्तव में क्या मतलब है?
\n\nशुरुआत के लिए — एक स्पष्ट परिभाषा। Pass-by-value का अर्थ है कि मेथड को कॉल करते समय, वेरिएबल के मान की एक प्रतिलिपि बनाई जाती है, और यह प्रतिलिपि मेथड को पास की जाती है। मूल वेरिएबल (कॉल करने वाले कोड में) अछूता रहता है।
\n\nPass-by-reference — यह तब होता है जब मेथड को मूल वेरिएबल का एक संदर्भ दिया जाता है, और मेथड सीधे उसके मान को बदल सकता है (उदाहरण के लिए, ऑब्जेक्ट को बदल सकता है)। Java में ऐसा नहीं है।
\n\nअब — उदाहरणों पर आते हैं।
\n\nउदाहरण 1: प्रिमिटिव टाइप्स (int, double, boolean)
\n\nप्रिमिटिव के साथ सब कुछ स्पष्ट है। आइए एक सरल कोड लिखें:
\n\npublic class Main {\n public static void main(String[] args) {\n int x = 10;\n changePrimitive(x);\n System.out.println("मेथड के बाद x = " + x); // 10\n }\n\n public static void changePrimitive(int value) {\n value = 20; // प्रतिलिपि बदल रहे हैं\n }\n}\n\nआउटपुट:
\nमेथड के बाद x = 10\n\nसब कुछ तार्किक है: हमने संख्या 10 की एक प्रतिलिपि पास की, मेथड के अंदर प्रतिलिपि को 20 में बदल दिया, लेकिन मूल वही रहा। यह शुद्ध pass-by-value है।
\n\nउदाहरण 2: ऑब्जेक्ट्स (स्ट्रिंग्स, लिस्ट्स, अपनी क्लासेस)
\n\nअब सबसे दिलचस्प भाग। देखिए:
\n\npublic class Main {\n public static void main(String[] args) {\n StringBuilder sb = new StringBuilder("Hello");\n changeStringBuilder(sb);\n System.out.println("मेथड के बाद: " + sb); // Hello World!\n }\n\n public static void changeStringBuilder(StringBuilder builder) {\n builder.append(" World!"); // ऑब्जेक्ट की सामग्री बदल रहे हैं\n }\n}\n\nआउटपुट:
\nमेथड के बाद: Hello World!\n\n«अहा! — आप कहेंगे। — ऑब्जेक्ट बदल गया! तो यह pass-by-reference है!»
\n\nनहीं। यह वह जाल है जिसमें 90% शुरुआती फंस जाते हैं। आइए समझते हैं कि वास्तव में क्या हुआ।
\n\nवेरिएबल sb में क्या संग्रहीत है?
\n\nJava में, ऑब्जेक्ट टाइप का वेरिएबल (उदाहरण के लिए, StringBuilder, List, String) स्वयं ऑब्जेक्ट को संग्रहीत नहीं करता। यह मेमोरी में ऑब्जेक्ट के लिए एक रेफरेंस (पता) संग्रहीत करता है। ऑब्जेक्ट स्वयं हीप (heap) में कहीं स्थित होता है।
जब आप sb को मेथड में पास करते हैं, तो आप इस रेफरेंस की एक प्रतिलिपि पास करते हैं। यानी पते की एक प्रतिलिपि। मूल और प्रतिलिपि दोनों एक ही ऑब्जेक्ट की ओर इशारा करते हैं। इसलिए, जब आप रेफरेंस की प्रतिलिपि के माध्यम से ऑब्जेक्ट की सामग्री बदलते हैं, तो ये परिवर्तन मूल रेफरेंस के माध्यम से भी दिखाई देते हैं।
लेकिन! यदि आप मेथड के अंदर पैरामीटर को एक नया ऑब्जेक्ट असाइन करने का प्रयास करते हैं, तो मूल रेफरेंस नहीं बदलेगा। आइए साबित करें:
\n\npublic class Main {\n public static void main(String[] args) {\n StringBuilder sb = new StringBuilder("Hello");\n changeReference(sb);\n S