آموزش جاوا - قسمت چهارم

1396/9/15 محمد چنگانی 6348

java-dataTypes-final-variable

با سلام و عرض ادب خدمت شما علاقه مندان به زبان برنامه نویسی جاوا . در جلسه قبلی در مورد کپسوله‌سازی و سازنده در سی شارپ صحبت کردیم. در این جلسه در مورد انواع data Type ها در جاوا و تفاوت‌های اون‌ها هست.

به طور کلی ما دو نوع کلی در جاوا داریم. نوع primitive و reference . که یکسری تفاوت‌های کلی با هم دارند که این جلسه در موردشون حرف میزنیم.

انواع داده ای در جاوا

در جاوا ما ۸ نوع دیتاتایپ primitive data types (انواع داده ای) داریم که عبارت‌اند از:

  • byte
  • short
  • int
  • long
  • float
  • double
  • char
  • boolean

و سایر انواع داده ای دیگر در گروه reference قرار میگیرند. مثلا هر نمونه‌ای که از کلاس Student که جلسه قبلی تعریف کردیم. با این تقسیم بندی میتوانیم ویژگی هرکدام را به راحتی بیان کرد.

تفاوت انواع داده ای primitive و reference 

در ادامه تفاوت های انواع داده ای primitive  و reference را از نظر دسترسی به حافظه، انتصاب مقدار، مقایسه داده ها، نوع بازگشت یا Return و مقدار پیشفرض را بیان می کنیم.

دسترسی به حافظه:

برای primitive ها وقتی شما متغییر primitive را فراخوانی میکنید دقیقا به خانه‌ای از حافظه دسترسی دارید که مقدار این متغییر در آنجا ذخیره شده است.

برای reference با فراخوانی در واقع شما به خانه‌ای از حافظه دسترسی دارید که آدرس از حافظه در آنجا قرار دارد که اطلاعات نمونه مورد نظر ذخیره شده است.

انتصاب مقدار (Assignment):

در primitive ها، با انتصاب یک مقدار در واقع واقعا مقدار آن متغییر تغییر می کند ولی در reference ها فقط آدرسی که اشاره شده است تغییر می‌کند.

در واقع به زبان خیلی ساده‌تر  متغییر‌های reference حالت اشاره‌گر دارند.

مقایسه:

شما میتوانید دو متغییر primitive رو با استفاده از علائم مقایسه‌ای مقایسه کنید:

int a = 9;
int b = 10;
a == b, a<= b, …

ولی اگر برای دو متغییر reference از این روش استفاده کنید در واقع شما دارین آدرس‌هایی که به هم اشاره میکنند را با هم مقایسه می‌کنید:

String a = “a”

String b = “a”

a == b ⇒ false

برای مقایسه دو متغییر reference باید از متد equals استفاده کرد:

a.equals(b)

دقت داشته باشید برای مقایسه مثلا دو نمونه از کلاس student لازم هست شما متد equals کلاس Student را پیاده سازی کنید

مقایسه دو نتغیر با استفاده از equals

در مورد نحوه پیاده‌سازی متد equals باید نهایت دقت را حائز کنید. چون ورودی این متد به صورت object است و لازم است ابتدا تبدیل به نمونه Student شود و بعد بر اساس معیار برنامه ما شرط تساوی چک شود. مثلا در عکس بالا شرط تساوی دو نمونه Student به این صورت است که دارای id یکسانی باشد. شاید شما فکر کنید دو نمونه از کلاس Student وقتی برابر هستند که usernmae های یکسانی داشته باشند. شما میتوانید هر نوع شرط تساوی را خود پیاده سازی کنید.

نکته بعدی اینکه امضای متد equals باید به همین صورت باشد و نباید تغییر کند.

بازگشت (return)

وقتی شما یک متغییر primitive را return میکنید در واقع مقدار آن بر میگردد. ولی وقتی شما یک نمونه reference را return میکنید در واقع آدرس آن نمونه برگشت داده میشود.

مقدار پیشفرض:

متغییرهای primitive مقدار پیشفرض دارند که متفاوت هستند مثلا مقدار پیش فرض برای متغیرهای عددی مقدار صفر و برای نوع boolean مقدار false هست:

int a;
System.out.println(a);

برای نمونه های reference مقدار پیشفرضی وجود ندارد و null هستند:

Student a;
System.out.println(a);
پذیرش null:

به متغییرهای primitive نمی‌توان مقدار null نسبت داد ولی به متغییرهای reference میشود مقدار null داد. در واقع معنی null بودن مقدار یک متغییر این است که به هیج‌کجا از حافظه اشاره نمی‌کند.

int a = null; //error
Student a = null;

برای روشن شدن مسئله به مثال های زیر دقت کنید (حتما امتحان کنید و خروجی را ببینید):

بررسی مقدار پیش فرض داده ها

بررسی مقدار پیش فرض داده ها در جاوا

حتما این دو مثال رو اجرا کنید. تا متوجه تفاوت بین این دو شوید.

متغییر های primitive با پاس دادن یا return کردن در واقع مقدارشون جابجا می‌شوند. در صورتی که در reference ها آدرس آن ها جابجا میشود و بنابراین از هرکجا که تغییری بر روی نمونه انجام شود تغییر میکند.

نکته بعدی اینکه در خط ۹ام وقتی عمل انتصاب برای نمونه b انجام میشود در واقع هم نمونه b و هم نمونه a به یک مکان در حافظه اشاره میکنند. یعنی هر دو نمونه یکی هستند ولی با نام مختلف! پس آدرسی که اشاره میکنند نیز یکسان هستند.

در اینجا نکته‌ای وجود دارد و آن این است که ما میتوانیم یک کلاس و نمونه را طوری تعریف کنیم که یکسری از این ویژگی‌ها را نداشته باشد و یا مثلا خاصیت اشاره‌گری را نداشته باشد که به آنها اصطلاحا Immutable objects گفته می‌شود که خارج از بحث کلاس ما است. در صورتی که قصد داشتید بیشتر در مورد این نوع نمونه ها بدانید می توانید تاپیک های جاوا و داکویمنت های جاوا را مطالعه کنید.

مفهوم final در جاوا

مورد دیگری که این جلسه در مورد آن صحبت می‌کنیم مفهوم final در جاوا هست. وقتی یک متغییر به صورت final تعریف می شود به این معنی است که از لحظه تعریف تا انتها این متغییر یا نمونه قابلیت تغییر مقدار یا آدرس را خود را ندارد:

final int a = 5;

final Student b = new Student();

a = 8; // error

b = new Student(); // error

Student c = new Student();

b = c; //error

b.setId(5); //correct

برای نمونه‌های reference که به صورت final تعریف شده اند نمیتوان آن نمونه را دوباره new یا دوباره انتصاب کرد ولی می توان مقادیر داخل آن کلاس را تغییر داد. یعنی مثلا میتوان مقدار id نمونه b را در بالا تغییر داد.

خوب سوالی که پیش ‌میاد این اشت که چرا ما باید خود را محدود کنیم و متغییرها را به صورت final تعریف کنیم؟

جواب این سوال فقط بالا بردن بازدهی کامپایل و افزایش سرعت اجرا و همچینین کاهش خطا است. شما وقتی یک نمونه یا متغییر را به صورت final تعریف می کنید، عملیات کامپایل جاوا را خیلی راحت میکنید و در واقع به جاوا میگید که مطمئن باش تا آخر قرار نیست مقدار این متغییر یا نمونه تغییر کند بنابراین خیلی از کارهای اضافه‌ای که لازم است را انجام نمیدهد و این باعث می‌شود ارجاع کمتری به حافظه داشته باشید و سرعت به مراتب بالا میرود.

از نظر برنامه نویسی هم خطای برنامه نویس کاهش پیدا می کند. چون وقتی شما مطمئن هستید متغییری دارید که نباید مقدارش تغییر کند آن را final تعریف میکنید و اگر در صورتی اشتباها تغییر پیدا کرد در زمان کامپایل به شما خطا داده می‌شود.

نکته بعدی اینکه وقتی متغییری را final در نظر میگیرید باید حتما آن را مقداردهی کنید و نمی توان به این صورت عمل کرد:

final int a;

a = 5;

متغییر final باید همیشه مقدار دهی اولیه شود و تا آخر برنامه این متغییر همین مقدار با همین مقدار باقی می ماند.

اگر یک field از یک کلاس را به صورت final تعریف کنیم باید یا در لحظه تعریف مقدار دهی شود:

private int id =5;

و یا اینکه حتما در سازنده مقدار دهی شود:

private int id;
public Student(int id){
this.id = id;
}

دقت شود اگر شما چند سازنده دارید باید در تک تک آن‌ها فیلدهای final را مقدار دهی کنید. در غیر این صورت در برنامه خطا داده می‌شود.

تعریف متغیر final در جاوا

اگه دقت کنید از زمانی که ما فیلدها را به صورت final تعریف کردیم متدهای set مربوط به آن ها دارای خطا است و با توجه به تعریف مفهوم final هم درست است. چون نمیتوانیم آنها را تغییر دهیم، پس نمیتوانیم هم متد set برای آنها داشته باشیم.

اگه به خاطر داشته باشید گفتیم برنامه نویسی یک سری قانون دارد که حتما باید رعایت شوند (با اینکه رعایت نشدن آنها در اجرا شدن یا نشدن برنامه تاثیری ندارند)، یکی دیگه از این قوانین این است که هر متغییر یا نمونه یا فیلد که تعریف می‌شود باید همیشه final باشد مگه اینکه خلاف آن ثابت شود.

در واقع ما همیشه همه را باید final کنیم مگر جاهایی که واقعا نمی‌شود. مثلا فیلد name برای Student مقداری است که شاید توسط دانشجو تغییر کند پس نمی‌توان آن را final در نظر گرفت ولی مفهومی شبیه id یا username رو دانشجو نمیتواند تعییر دهد و همیشه ثابت می‌ماند پس باید final تعریف شود.

به طور کلی هر موقع هر چیزی را خواستیم تعریف کنیم final تعریف کنیم و اگه جلوتر به بن‌بست خوردیم و دیدیم واقعا لازم است که مقدار آن تغییر کند از حالت final خارج کنیم.

قواعد نگارشی جاوا:

۱- همه متغییرها تا جایی که امکان دارد به صورت final تعریف شوند.

۲- متغییرهایی که طول عمر محدودی دارند می‌توانند به صورت final نباشند و همچنین از کلمات با معنی برای آن ها استفاده نشود:

Example: for(int i = 0; i < n; i++){}

۳- برای فیلدهای boolean بجای استفاده از get از پیشوند is استفاده شود:

private boolean open;
public boolean getOpen() // wrong
public boolean isOpen() // correct

۴- استفاده از کلمه compute برای متدهایی که چیزی را محاسبه می‌کنند:

valueSet.computeAverage(),
fetcher.computeRemainingCapacity()

 

کلمات کلیدی

آموزش جاوا آموزش java