آیا جاوا pass-by-reference است یا pass-by-value؟
بحث در مورد pass-by-reference بودن یا pass-by-value بودن زبان جاوا موضوعی قدیمی است اما گاها موجب سردرگمی افراد تازهکار میشود. در این مطلب این موضوع را بررسی میکنیم.
علت بسیاری از ابهامات به وجود آمده به خاطر تعریف متفاوت افراد از reference است. متاسفانه عدهای pointer را refrence نامیدند و همین مساله باعث شد افراد تازهکار سردرگم شوند. چرا که این referenceها از طریق مقادیرشان پاس داده میشوند.
مثال آن مشابه زیر است:
public static void main(String[] args) { Dog aDog = new Dog("Max"); foo(aDog); if (aDog.getName().equals("Max")) { //true System.out.println("Java passes by value."); } else if (aDog.getName().equals("Fifi")) { System.out.println("Java passes by reference."); } } public static void foo(Dog d) { d.getName().equals("Max"); // true d = new Dog("Fifi"); d.getName().equals("Fifi"); // true }
در این مثال aDog.getName کماکان Max را برمیگرداند. مقدار aDog درون main توسط تابع foo بازنویسی نمیشود. چون که مقدار شی به تابع پاس شده است. اگر به شکل pass-by-refrence بود، آنگاه aDog.getName در main بعد از اجرای تابع foo، نام Fifi را برمیگرداند.
به همین ترتیب:
Dog aDog = new Dog("Max"); foo(aDog); aDog.getName(). equals("Fifi"); // true public void foo(Dog d) { d.getName().equals("Max"); // true d.setName("Fifi"); }
در واقع حتی در مشخصات زبان جاوا هم آمده است که جاوا pass-by-value است و چیزی به اسم pass-by-reference در آن وجود ندارد. میتوان مثالهای دیگری هم برای اثبات این حرف ارائه کرد. به عنوان مثال اگر جاوا pass-by-reference میبود بایستی امکان swap کردن اشیا مشابه C در آن وجود میداشت.
اما برای اینکه دیگر شک و ابهامی باقی نماند بیایید تخصیص حافظه در زمان اجرا را در کد زیر بررسی کنیم:
public class Foo { private String attribute; public Foo(String a) { this.attribute = a; } public String getAttribute() { return attribute; } public void setAttribute(String attribute) { this.attribute = attribute; } } public class Main { public static void main(String[] args) { Foo f = new Foo("f"); changeReference(f); // It won't change the reference! modifyReference(f); // It will change the object that the reference variable "f" refers to! } public static void changeReference(Foo a) { Foo b = new Foo("b"); a = b; } public static void modifyReference(Foo c) { c.setAttribute("c"); } }
Foo f = new Foo(“f”); (۱
این جمله یک نمونه از کلاس Foo را میسازد که یک صفت آن به f مقداردهی اولیه شده است. reference این نمونه ساخته شده به متغیر f نسبت داده شده است.
public static void changeReference(Foo a) (۲
وقتی این اجرا میشود یک reference از نوع Foo با نام a تعریف میشود که مقدار دهی اولیه ندارد.
changeReference(f) (۳
وقتی که این متد فراخوانی میشود، رفرنس a به شئی که به عنوان آرگومان پاس شده است انتساب داده میشود.
Foo b = new Foo(“b”); (۴ در اولین متد
دقیقا مشابه اولین گام است و یک نمونه از Foo را میسازد و به b انتساب میدهد.
a = b; (۵
این نقطه مهم است. در اینجا سه reference داریم و وقتی این عبارت اجرا میشود، a و b به یک نمونه یکسان درون تابع اشاره میکنند. توجه کنید: f تغییر نکرده است و مثل قبل به همان نمونه اشاره دارد.
modifyReference(Foo c); (۶
حالا وقتی که این عبارت اجرا میشود، c ساخته شده و به شئی با صفت f انتساب داده میشود.
c.setAttribute(“c”); (۷
این دستور میتواند صفت شئی که رفرنس c به آن اشاره میکند را تغییر دهد و پس متعاقبا شئ که رفرنس f به آن اشاره میکند نیز تغییر میکند.
امیدواریم این توضیحات این مساله را برای همیشه برایتان حل کرده باشد.
منبع:
ظاهرا تصویر بخش 6 اشتباه شده . باید مانند تصویر بخش 2 باشد
با تشکر از توضیحات جامع شما
شاید ذکر این نکته تکمیلی مفید باشد که :
به دلیل کپی شدن رفرنس اشیا در فراخوانی متدها، شما هیچ گاه نمی توانید متدی بنویسید که دو آبجکت را با یکدیگر swap می کند. عملیات swapping باید در بلوک کد و بدون استفاده از متد اضافه تری صورت پذیرد.
در واقع رفرنس آبجکت ها (و نه خود آبجکت) به صورت by-value به بدنه متد ارسال می شوند.
با تشکر