فراخوانی سازندهها در وراثت
یکی از نکاتی که هنگام ارثبری از کلاسها باید به آن توجه داشت، سازنده کلاس پدر (Superclass) است. به کلاس Employee که در قسمت قبل آن را نوشتیم یک سازنده اضافه میکنیم:
public class Employee { protected String name; protected double salary; protected Date hireDate; public Employee(String name, double salary) { System.out.println("Employee Constructor Called."); this.name = name; this.salary = salary; } public String getName() { return name; } public Date getHireDate() { return hireDate; } }
در این سازنده یک دستور چاپ وجود دارد که عبارت “سازنده کلاس Employee فراخوانی شد“ را چاپ میکند و در خطوط بعدی آن به فیلدهای name و salary مقداردهی شده است (با انتساب مقادیر پارامترهای سازنده به فیلدهای کلاس).
حال به کلاس Accountant یک سازنده با یک پارامتر اضافه میکنیم:
public class Accountant extends Employee { private int workingHours; public Accountant(int workingHours) { System.out.println("Accountant Constructor Called."); this.workingHours = workingHours; } }
همانطور که دیدید در این سازنده علاوه بر چاپ عبارت “سازنده کلاس Accountant فراخوانی شد“ مقدار پارامتر workingHours را نیز به فیلد workingHours کلاس خود نسبت دادیم.
همه چیز تا الان درست به نظر میآید اما یک سوال مهم وجود دارد: میدانیم که کلاس Accountant از کلاس Employee ارثبری دارد و به همین دلیل اگر شیئی از کلاس Accountant ایجاد کنیم اعضای کلاس Employee نیز در آن شی وجود دارند. اگر به شکل زیر شیئی از کلاس Accountant ایجاد کنیم:
Accountant ac = new Accountant(8);
به سازنده کلاس Accountant مقدار ۸ را دادیم تا آن را به فیلد workingHours نسبت دهد. اما تکلیف فیلدهای کلاس Employee که حالا در این شی وجود دارند چه میشود؟ چگونه باید به فیلدهایی که کلاس Accountant از کلاس Employee به ارثبرده مقدار دهیم؟
همانطور که میدانید قسمتی از کلاس Accountant در کلاس Employee قرار دارد (اعضای تعریف شده در کلاس Employee) و قسمتی از آن هم در خود کلاس Accountant قرار دارد (فیلد workingHours) بنابراین میگوییم که سازنده کلاس Employee مسئول تولید و مقداردهی به آن قسمتی است که در کلاس Employee قرار دارد و سازنده کلاس Accountant نیز مسئول مقداردهی به قسمتی که در کلاس Accountant است لذا برای ایجاد یک شی از کلاس Accountant باید هر دو قسمت کلاس ساخته شود.
پس باید در سازنده کلاس Accountant سازنده کلاس Employee را فراخوانی کنیم تا هنگام ایجاد شی از کلاس Accountant اعضای موجود در Employee نیز مقدار دهی شوند.
سازنده کلاس Accountant را تغییر میدهیم و دو پارامتر به آن اضافه میکنیم. سپس باید سازنده کلاس پدر یعنی Employee را فراخوانی کنیم که این کار را با super انجام میدهیم:
public Accountant(String name, double salary, int workingHours) { super(name, salary); System.out.println("Accountant Constructor Called."); this.workingHours = workingHours; }
همانطور که در کد معلوم است با استفاده از عبارت super سازنده کلاس Employee را فراخوانی کرده و پارامترهای name و salary را به آن دادیم. البته در این سازنده میتوان به جای name و salary هر اسمی نوشت اما باید به ترتیب قرار دادن آن در super توجه داشت.
اگر شیئی از کلاس Accountant ایجاد کنید نتیجه زیر را در پنجره کنسول خواهید دید:
Employee Constructor Called.
Accountant Constructor Called.
از این خروجی متوجه میشویم که هنگام ایجاد شی از کلاس Accountant قبل از فراخوانی سازنده کلاس Accountant سازنده کلاس پدر آن یعنی Employee فراخوانی شده است.
نکته مهم: فراخوانی سازنده کلاس پدر با super باید همیشه اولین دستور در سازنده باشد.
سلسله مراتب وراثت
در کلاسهای Accountant و Employee سلسله مراتب وراثت به صورت زیر است:
Employee -> Accountant
یعنی در این سلسله مراتب فقط دو کلاس وجود دارد که کلاس Accountant از Employee ارثبری دارد. اما این سلسله مراتب فقط محدود به دو کلاس نیست بلکه این زنجیره میتواند به هر تعدادی باشد. مثلا میتوانیم کلاسی به نام Person (شخص) ایجاد کنیم که کلاس Employee از آن ارثبری داشته باشد:
Person -> Employee -> Accountant
به این ترتیب کلاس Accountant هم اعضای کلاس Employee و هم اعضای کلاس Person را به ارث خواهد برد. رابطه Is-A به این صورت خواهد شد: هر حسابدار یک کارمند است و هر کارمند یک شخص است.
Override کردن متدها
اگر در یک رابطه وراثتی متدی در کلاس فرزند وجود داشته باشد و متدی با همان امضا و نوع داده بازگشتی (قبلا گفتیم که امضای متد شامل نام و تعداد و نوع پارامترهای آن متد است) در کلاس پدر آن نیز وجود داشته باشد میگوییم که کلاس فرزند آن متد را Override کرده است.
مثال:
کلاسی به نام Person داریم:
public class Person { public void sayHello() { System.out.println("Hello"); } }
کلاس دیگری به نام FrenchPerson داریم که از Person ارث بری دارد:
public class FrenchPerson extends Person { }
اگر شیئی از کلاس FrenchPerson ایجاد کنیم و متد sayHello آن را فراخوانی کنیم عبارت Hello در کنسول چاپ خواهد شد. اما یک فرد فرانسوی اینگونه سلام نمیکند. میخواهیم متد sayHello در کلاس Person را در کلاس فرزند آن Override کنیم:
public class FrenchPerson extends Person { public void sayHello() { System.out.println("Bonjour"); } }
حال اگر از شی ساخته شده از کلاس FrenchPerson متد sayHello را فراخوانی کنید دیگر عبارت Hello چاپ نخواهد شد چون ما آن متد را Override کردیم و بنابراین عبارت Bonjour چاپ میشود.
اما گاهی اوقات لازم است تا در یک متد Override شده متد کلاس پدر آن هم فراخوانی شود. میتوان با super به صورت زیر این کار را انجام داد:
public void sayHello() { super.sayHello(); System.out.println("Bonjour"); }
حال اگر متد sayHello از کلاس FrenchPerson را فراخوانی کنید هر دو عبارت Hello و Bonjour چاپ خواهد شد.
نکته: فراخوانی متد کلاس پدر با عبارت super میتواند در هرجای متد باشد و الزاما نیازی به استفاده از super در خط اول متد نیست.
نکته: اگر متدی که از کلاس پدر Override کردید پارامتر داشته باشد آنگاه هنگام فراخوانی آن با super باید پارامترهای مورد نیازش را هم به آن بدهید. به عنوان مثال اگر متد sayHello یک پارامتر رشتهای به نام name دریافت میکرد به صورت زیر عمل میکردیم:
super.sayHello("Bahram");
super
از super برای فراخوانی سازنده کلاس پدر استفاده کردیم اما super کاربردهای دیگری نیز دارد. یکی از کاربردهای آن دسترسی به اعضای کلاس پدر است.
مثال: کلاسی به نام A با یک فیلد به نام i داریم:
public class A { protected int i; }
کلاس دیگری به نام B داریم که از A ارثبری دارد. در این کلاس هم فیلدی به نام i تعریف شده است:
public class B extends A { int i; public void setNumber() { i = 20; } }
در متد setNumber میبینید که مقدار ۲۰ را به i نسبت دادیم. اما کدام i ؟ کلاس B هم فیلد i کلاس A را به ارث برده و هم خود فیلدی به نام i دارد. فیلد i ای که در کلاس B وجود دارد فیلد i ارث برده از کلاس A را مخفی (Shadow) میکند بنابراین برای دسترسی به فیلدی از کلاس پدر در کلاس فرزند میتوانیم از super استفاده کنیم. اگر کد متد setNumber را به صورت زیر تغییر دهیم:
public void setNumber() { super.i = 20; }
آنگاه مقدار i به فیلد i کلاس A نسبت داده میشود.
نکته: فقط در صورتی برای دسترسی به فیلدهای کلاس پدر به super نیاز داریم که فیلدی در کلاس فرزند با همان نام وجود داشته باشد و فیلد کلاس پدر خود را مخفی کرده باشد. در مثال بالا اگر در کلاس B فیلدی به نام i وجود نداشت میتوانستیم بدون استفاده از عبارت super و به صورت مستقیم به فیلد i موجود در کلاس A دسترسی داشته باشیم.
آموزشها بهنظرم خیلی حرفهای و با جرئیات و نکات اصولی همراهه. مرسی و خسته نباشی.
ممنون از لطف شما
ایول شما عالی هستین ولی ایکاش بازم اموزش های اینطوری از جاوا بزارید مثلا تکنیک هارو اموزش بدین یا به GUI بپردازین
خواهش می کنم شما لطف دارید. قصد دارم در آینده یک سری آموزش های نهایتا ۲-۳ قسمتی منتشر کنم اما نوشتن آموزش های قدم به قدم و طولانی به دلیل کمبود وقت در توانم نیست.
ولی اگه یه اموزش کامل مثل جاوا در موزد git هم بزارید خیلی باحالید !
من که تا الان یه آموزش فارسی درست حسابی در مورد git پیدا نکردم
سلام پیشنهاد میکنم که در قسمت constructor این نکته رو هم اضافه کنید که () super (یا فراخانی کانستراکتور کلاس پدر ) باید حتما در خط اول باشد . مرسی
خیلی ممنون بابت زحماتتون. خوشحالم که با شما آشنا شدم.