نکتهای که وجود دارد این است که بسیاری از برنامه نویسان جاوا معتقدند که جاوا Call By Value است و عدهای دیگر نیز معتقدند که جاوا Call By Reference است. اینکه جاوا را چه مینامند اصلا اهمیتی ندارد، بلکه مهم این است که شما رفتار جاوا را بدانید که در هر وضعیت چگونه عمل میکند.
نگاهی کوتاه به مفاهیم گذشته
قبل از اینکه وارد بحث اصلی شویم، بهتر است نگاهی کوتاه به مفاهیم گذشته کنیم. اطلاع و تسلط داشتن کامل به مفاهیمی که در گذشته آموزش داده شده است، بسیار مهم و لازم برای فهمیدن و درک مطالب این جلسه است. پس با دقت مطالعه کنید.
همانطور که میدانید در جاوا دو نوع داده وجود دارد که عبارت هستند از دادههای اولیه (Primitive Data Type) و دادههای ارجاعی (Reference Data Type). دادههای اولیه (Primitive Data Type) هشت نوع بودند: byte, short, int, long, char, float, double, boolea. این هشت نوع داده به صورت پیشفرض در جاوا تعریف شدهاند و استفاده از آنها در یک مثال ساده به صورت زیر است:
package ir.zoomit;
public class MainApp {
public static void main(String[] args) {
int integerNumber = 10;
char character = 'A';
boolean b = true;
}
}
با استفاده از Primitive Data Typeها میتوانیم دادههای پیچیدهتری را بسازیم. به عنوان مثال کلاس String که برای کار با رشتهها استفاده میشود، در دل خودش از دادهی کارکتر (char) استفاده کرده است. String یک کلاس است، بنابراین جزء دادههای ارجاعی (Reference Data Type) به حساب میآید. به عبارت دیگر تمام کلاسها که از روی آنها اشیائی ایجاد میشود، جزء دادههای ارجاعی هستند و فقط و فقط آن هشت دادهای که در بالا گفته شد جزء دادههای پایه به حساب میآیند.
چند نکته در مورد دادههای پایه و ارجاعی
دقیقا منظور از ارجاع یا Reference چیست؟ به طور کلی برای استفاده از دادههای ارجاعی، حتما باید ابتدا از روی آنها یک آبجکت ساخت. به صورت زیر:
package ir.zoomit;
public class MainApp {
public static void main(String[] args) {
Person p = new Person(); // Object Creation OR Instantiation
}
}
class Person {
}
در کد بالا دو کلاس وجود دارد. یکی کلاس اصلی (MainApp) که در آن متد معروف main پیادهسازی شده است و دیگری کلاس Person که هیچ پیاده سازی ندارد. همانطور که مشاهده میکنید در داخل متد main یک آبجکت از روی کلاس Person ساختهایم.
در بسیاری از کتابها و منابع آموزشی به اشتباه گفته میشود که p در کد بالا شی یا آبجکت است. در صورتی که کاملا اشتباه است. ما در آموزشهای قبلی هم در مورد این موضوع صحبت کردیم، اما بهتر است دوباره نگاهی سطح پایینتر به این موضوع داشته باشیم.
همانطور که میدانید برای ساختن یک شی از روی یک کلاس، باید آن کلاس را new کنیم. هنگامی که با استفاده از عملگر new اقدام به ساخت یک شی از روی یک کلاس میکنیم، عملگر new در واقع دو کار را انجام میدهد. ابتدا یک شی جدید در حافظهی Heap ایجاد میکند و سپس یک Reference یا ارجاعی از آن شی ساخته شده را برمیگرداند. یعنی ما با استفاده از آن ارجاع، میتوانیم به شی ساخته شده در حافظه دسترسی داشته باشیم. برای اینکه بتوانیم با استفاده از ارجاع به شیئی در حافظه دسترسی داشته باشیم، باید ارجاع را در یک متغیری ذخیره کنیم. این متغیر در کد بالا، p است. بنابراین p شی نیست، بلکه یک Reference یا ارجاعی به شی در حافظه است.
نکتهی دیگری که باید از آن اطلاع داشته باشید و قبلا هم به آن اشاره شده است، این است که در تکه کد زیر:
Person p = new Person();
متغیر p در حافظهی Stack (استک) ایجاد شده است و آبجکت یا شی در حافظهی Heap «هیپ».
برای درک بهتر مسئلهی فوق، به تصویر زیر توجه کنید:
تصویر فوق به صورت دقیق این مسئله را روشن میکند، بنابراین با دقت توجه کنید. در این عکس در یک متُدی با نام Method1 دو داده از نوع دادههای اولیه یا Primitive Data Type تعریف کرده است و بعد هم از روی یک کلاسی با نام Class1 آبجکتی ایجاد کرده است. به Line1 (گوشهی بالا سمت چپ تصویر) توجه کنید. وقتی که در برنامه یک داده از نوع عدد صحیح تعریف شده است، در حافظهی Stack این متغیر ایجاد و مقداردهی شده است. توجه کنید که Primitive Data Typeها همانطور که از نامشان پیداست، دادههای اولیه هستند و نمیتوانیم آنها را new کنیم. new کردن فقط مختص کلاسها است که از روی آنها اشیائی ایجاد میشود. بنابراین وقتی در برنامه دادهای از نوع دادههای اولیه تعریف میکنیم، آن داده در حافظهی Stack ذخیره و مقداردهی میشود. اگر با درس Data structure یا ساختمان دادهها آشنایی داشته باشید، مبحثی است با نام Stack یاپُشته که در آن اصطلاح LIFO را برای Stack در نظر گرفتهاند که مخفف: Last In First Out است. یعنی اینکه آخرین دادهای که وارد Stack میشود، اولین دادهای است که از آن خارج میشود. اگر به تصویر فوق نیز نگاه کنید، استک را همانند یک لیوان کشیده است که یک سَر آن بسته و یک سَر دیگر باز است. دادهها وقتی وارد استک میشوند، روی یکدیگر قرار میگیرند. اگر به خط بعدی برنامه نگاه کنید، دوباره دادهای از نوع دادههای اولیه تعریف و مقداردهی شده است. حالا به Line2 توجه کنید. همانطور که مشاهده میکنید، در استک متغیر y روی متغیر i قرار گرفته است. پس تا اینجای کار، متغیر y آخرین متغیر یا دادهای است که وارد Stack شده است، پس اولین متغیر یا دادهای است که از استک خارج میشود.
حالا به سراغ ادامهی کد میرویم. در ادامه برنامه میخواهد از روی یک کلاس، آبجکتی در حافظه ایجاد کند و ارجاع یا Reference آن آبجکت را در متغیری با نام cls1 ذخیره کند. حالا به Line3توجه کنید. متغیر cls1 در حافظهی Stack ایجاد شده است و همانطور که در تصویر نیز مشاهده میکنید، در مقابل آن و در داخل پرانتز عبارت (ref) را نوشته است که منظور همان Reference یا ارجاع است. باز هم به تصویر دقت کنید. در Line3 حافظهی Heap را هم کشیده است و متغیرcls1 در حال اشاره کردن به آبجکتی است که در Heap ایجاد شده است. پس با توضیحات فوق باید مفهوم Reference یا ارجاع را کاملا درک کرده باشید.
توضیحات در مورد مفهوم ارجاع یا Reference به طور کلی گفته شد. اما در اینجا قصد داریم به یک نکته اشاره کنیم تا یک سوءتفاهم را برطرف کنیم.
همانطور که قبلا هم در آموزشها اشاره شده است، در جاوا موجودی با نام زباله روب یا Garbage Collector وجود دارد. وظیفهی GC پاکسازی حافظهی Heap است. به عبارت دیگر Garbage Collector هر از چندگاهی به حافظهی Heap سر میزند و اشیاء به اصطلاح مُرده را پاک و حافظه را آزاد میکند. نکتهی بسیار مهم دقیقا همین جا است که Garbage Collector فقط و فقط حافظهی Heap را پاکسازی میکند. اگر به تصویر فوق نگاه کنید، در بخش exiting method، بعد از پایان برنامه حافظهی Stack خالی شده است، اما در حافظهی Heap آبجکت ساخته شده همچنان وجود دارد. نکته اینجا است که در تمام زبانهای برنامه نویسی (چه زبانی مثل جاوا که Garbage Collector دارد و به صورت خودکار حافظهی Heap را پاکسازی میکند، و چه زبانهایی مثل ++C که پاکسازی اشیاء نیز بر عهدهی برنامه نویس است)، حافظهی Stack به صورت خودکار پاک میشود و پاکسازی حافظهی Stack اصلا ربطی به وجود Garbage Collector ندارد و در تمام زبانها این کار به صورت خودکار انجام میشود. به این دلیل به نکتهی بالا پرداخته شد که در بعضی از کتابها و منابع آموزشی، برای توضیح نحوهی کار Garbage Collector، مثالی همانند مثال فوق میآورند و میگویند مثلا y، i یا cls1 توسط Garbage Collector پاک میشوند که کاملا غلط و اشتباه است.
ارسال پارامتر به متُد
متُدها نشان دهندهی رفتار یک برنامه هستند. وقتی یک متُد را تعریف میکنیم، برای آن با توجه به کاری که قرار است در برنامه انجام دهیم، پیاده سازیهای مختلفی در نظر میگیریم. به کد زیر توجه کنید: