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

1395/4/17 محمد چنگانی 4631

جلسه قبلی در مورد کپسوله‌سازی و سازنده صحبت کردیم.

بحث امشب در مورد انواع دیتاتایپ ها در جاوا و تفاوت‌های اون‌ها هست.

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

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

byte
short
int
long
float
double
char
boolean

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

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

برای 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 باید توجه کرد چون ورودی این متد به صورت 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 گفته می‌شود که خارج از بحث کلاس ما هست. در صورتی که خواستین بیشتر در مورد این نوع نمونه ها بدونید به این پیج مراجعه‌ کنید:

http://www.javapractices.com/topic/TopicAction.do?Id=29

https://docs.oracle.com/javase/tutorial/essential/concurrency/immutable.html

اگه از پارت سوالی هست من در خدمتم وگرنه بریم سراغ پارت دوم جلسه

مورد بعدی که این جلسه در مورد اون صحبت می‌کنیم مفهوم 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 تعریف کردیم متدهای 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()

دانلود PDF قسمت چهارم جاوا