در بخشهای قبلی چندین بار به ارثبری اشاره شد. در زبان جاوا، کلاسها میتوانند از کلاسهای دیگر مشتق شوند و از این طریق فیلدها و متدهای آن کلاسها را به ارث ببرند.
تعریفها: کلاسی که از کلاس دیگری مشتق شده باشد، زیرکلاس (Subclass) نامیده میشود (به نامهای Derived Class، Extended Class یا Child Class هم شناخته میشود). کلاسی که زیرکلاس از آن مشتق شده، فوقکلاس (Superclass) نام دارد (همچنین Base Class یا Parent Class).
به جز
Objectکه فوقکلاسی ندارد، هر کلاس دقیقاً یک فوقکلاس مستقیم دارد (ارثبری تکی — Single Inheritance). اگر فوقکلاس دیگری بهصورت صریح مشخص نشده باشد، هر کلاس بهصورت ضمنی زیرکلاسObjectاست.کلاسها میتوانند از کلاسهایی مشتق شوند که خودشان از کلاسهای دیگری مشتق شدهاند و به این ترتیب زنجیرهای ایجاد میشود که در نهایت به بالاترین کلاس یعنی
Objectختم میشود. به چنین کلاسی گفته میشود که از تمام کلاسهای زنجیرهی ارثبری که بهObjectبازمیگردد، مشتق شده است.
ایدهی ارثبری ساده اما قدرتمند است: وقتی میخواهید کلاس جدیدی بسازید و کلاسی از قبل وجود دارد که بخشی از کد مورد نیاز شما را شامل میشود، میتوانید کلاس جدیدتان را از آن کلاس موجود مشتق کنید. با این کار، میتوانید فیلدها و متدهای کلاس موجود را دوباره استفاده کنید بدون اینکه مجبور باشید خودتان آنها را بنویسید (و دیباگ کنید!).
زیرکلاس تمام اعضا (فیلدها، متدها و کلاسهای تو در تو) را از فوقکلاس به ارث میبرد. Constructorها عضو محسوب نمیشوند، بنابراین توسط زیرکلاسها به ارث نمیرسند، اما میتوان Constructor فوقکلاس را از داخل زیرکلاس فراخوانی کرد.
کلاس Object که در پکیج java.lang تعریف شده، رفتار مشترک تمام کلاسها را تعریف و پیادهسازی میکند — از جمله کلاسهایی که خودتان مینویسید. در پلتفرم جاوا، بسیاری از کلاسها مستقیماً از Object مشتق میشوند، کلاسهای دیگر از برخی از آنها مشتق میشوند و به همین ترتیب یک سلسلهمراتب از کلاسها شکل میگیرد.
[دیاگرام: ساختار سلسلهمراتبی ارثبری از Object در بالا تا زیرکلاسهای تخصصی در پایین]
در بالای این سلسلهمراتب، Object عمومیترین کلاس از همه است. کلاسهای نزدیک به انتهای سلسلهمراتب رفتارهای تخصصیتری ارائه میدهند.
کد نمونهی یک پیادهسازی ممکن از کلاس Bicycle که در بخش کلاسها و اشیا ارائه شده بود:
public class Bicycle {
// the Bicycle class has three fields
public int cadence;
public int gear;
public int speed;
// the Bicycle class has one constructor
public Bicycle(int startCadence, int startSpeed, int startGear) {
gear = startGear;
cadence = startCadence;
speed = startSpeed;
}
// the Bicycle class has four methods
public void setCadence(int newValue) {
cadence = newValue;
}
public void setGear(int newValue) {
gear = newValue;
}
public void applyBrake(int decrement) {
speed -= decrement;
}
public void speedUp(int increment) {
speed += increment;
}
}
تعریف کلاس MountainBike که یک زیرکلاس از Bicycle است، ممکن است به این شکل باشد:
public class MountainBike extends Bicycle {
// the MountainBike subclass adds one field
public int seatHeight;
// the MountainBike subclass has one constructor
public MountainBike(int startHeight,
int startCadence,
int startSpeed,
int startGear) {
super(startCadence, startSpeed, startGear);
seatHeight = startHeight;
}
// the MountainBike subclass adds one method
public void setHeight(int newValue) {
seatHeight = newValue;
}
}
MountainBike تمام فیلدها و متدهای Bicycle را به ارث میبرد و فیلد seatHeight و یک متد برای تنظیم آن را اضافه میکند. به جز Constructor، این دقیقاً همان کاری است که اگر کلاس MountainBike را از صفر با چهار فیلد و پنج متد نوشته باشید انجام میدهید. اما لازم نبود تمام این کارها را خودتان انجام دهید. این موضوع بهویژه زمانی ارزشمند است که متدهای Bicycle پیچیده باشند و زمان قابل توجهی صرف دیباگ کردن آنها شده باشد.
زیرکلاس تمام اعضای public و protected والد خود را به ارث میبرد، صرفنظر از اینکه در چه پکیجی باشد. اگر زیرکلاس در همان پکیج والد خود باشد، اعضای Package-Private والد را هم به ارث میبرد. میتوانید از اعضای به ارثرسیده به همان شکل استفاده کنید، آنها را جایگزین کنید، مخفی کنید یا با اعضای جدید تکمیلشان کنید:
super فراخوانی کند.زیرکلاس اعضای private والد خود را به ارث نمیبرد. اما اگر فوقکلاس متدهای public یا protected برای دسترسی به فیلدهای private خود داشته باشد، زیرکلاس هم میتواند از آنها استفاده کند.
یک کلاس تو در تو (Nested Class) به تمام اعضای private کلاس محصورکنندهاش دسترسی دارد — هم فیلدها و هم متدها را. بنابراین، یک کلاس تو در توی public یا protected که توسط زیرکلاس به ارث رسیده باشد، بهطور غیرمستقیم به تمام اعضای private فوقکلاس دسترسی خواهد داشت.
---دیدیم که یک شی از نوع دادهای کلاسی است که از آن نمونهسازی (Instantiate) شده است. مثلاً اگر بنویسیم
public MountainBike myBike = new MountainBike();
آنگاه myBike از نوع MountainBike است.
MountainBike از Bicycle و Object مشتق شده است. بنابراین یک MountainBike یک Bicycle است و همچنین یک Object است، و میتواند در هر جایی که به شی از نوع Bicycle یا Object نیاز باشد استفاده شود.
جهت عکس این موضوع لزوماً صادق نیست: یک Bicycle ممکن است MountainBike باشد اما لزوماً نیست. به همین ترتیب، یک Object ممکن است یک Bicycle یا یک MountainBike باشد اما لزوماً نیست.
Casting نشاندهندهی استفاده از شیای از یک نوع در جای نوع دیگری است، در میان اشیایی که توسط ارثبری و پیادهسازیها مجاز هستند. مثلاً اگر بنویسیم
Object obj = new MountainBike();
آنگاه obj هم یک Object و هم یک MountainBike است (تا زمانی که obj شی دیگری که MountainBike نیست به آن اختصاص داده شود). به این Casting ضمنی (Implicit Casting) میگویند.
از طرف دیگر، اگر بنویسیم
MountainBike myBike = obj;
خطای زمان کامپایل دریافت میکنیم چون کامپایلر نمیداند obj یک MountainBike است. اما میتوانیم با انجام Casting صریح (Explicit Casting) به کامپایلر بگوییم که تضمین میدهیم obj مقداری از نوع MountainBike خواهد داشت:
MountainBike myBike = (MountainBike)obj;
این Casting یک بررسی در زمان اجرا (Runtime Check) اضافه میکند که آیا obj واقعاً یک MountainBike هست یا نه تا کامپایلر بتواند با اطمینان فرض کند obj از نوع MountainBike است. اگر obj در زمان اجرا MountainBike نباشد، Exception پرتاب خواهد شد.
نکته: میتوانید از عملگر
instanceofبرای انجام یک آزمون منطقی روی نوع یک شی خاص استفاده کنید. این کار شما را از خطای زمان اجرا ناشی از Casting نادرست نجات میدهد. مثال:
if (obj instanceof MountainBike) {
MountainBike myBike = (MountainBike)obj;
}
---در اینجا عملگر
instanceofتأیید میکند کهobjبه یکMountainBikeارجاع دارد تا بتوانیم Casting را با این اطمینان انجام دهیم که هیچ Exceptionای در زمان اجرا پرتاب نخواهد شد.
یک تفاوت قابلتوجه بین کلاسها و Interfaceها این است که کلاسها میتوانند فیلد داشته باشند اما Interfaceها نه. علاوه بر این، میتوانید از کلاس نمونه بسازید که از Interface امکانپذیر نیست. همانطور که در بخش «شی چیست؟» توضیح داده شد، یک شی وضعیت خود را در فیلدهایی ذخیره میکند که در کلاسها تعریف شدهاند. یکی از دلایلی که زبان برنامهنویسی جاوا به شما اجازه نمیدهد از بیش از یک کلاس ارث ببرید، جلوگیری از مشکلات ناشی از ارثبری چندگانهی وضعیت (Multiple Inheritance of State) است — یعنی توانایی به ارث بردن فیلدها از چندین کلاس. مثلاً فرض کنید بتوانید کلاسی تعریف کنید که از چندین کلاس ارث ببرد. وقتی شیای از آن کلاس با نمونهسازی ایجاد کنید، آن شی فیلدهای تمام فوقکلاسهای آن کلاس را به ارث میبرد. اگر متدها یا Constructorهایی از فوقکلاسهای مختلف، فیلد یکسانی را نمونهسازی کنند، کدام اولویت خواهد داشت؟ به این دلیل که Interfaceها فیلد ندارند، نگران مشکلات ناشی از ارثبری چندگانهی وضعیت نخواهید بود.
[دیاگرام: مقایسهی ارثبری تکی از کلاسها در مقابل ارثبری چندگانه از Interfaceها]
ارثبری چندگانهی پیادهسازی (Multiple Inheritance of Implementation) یعنی توانایی به ارث بردن تعریف متدها از چندین کلاس. مشکلاتی مثل تداخل نامها و ابهام در این نوع ارثبری پیش میآید. وقتی کامپایلرهای زبانهای برنامهنویسی که از این نوع ارثبری پشتیبانی میکنند، با فوقکلاسهایی مواجه شوند که متدهایی با نام یکسان دارند، گاهی نمیتوانند تشخیص دهند کدام عضو یا متد را باید فراخوانی کنند. علاوه بر این، یک برنامهنویس ممکن است ناخواسته با اضافه کردن متد جدیدی به یک فوقکلاس، تداخل نام ایجاد کند. Default Methodها یک شکل از ارثبری چندگانهی پیادهسازی را معرفی کردند. یک کلاس میتواند بیش از یک Interface را پیادهسازی کند که ممکن است Default Methodهایی با نام یکسان داشته باشند. کامپایلر جاوا قوانینی برای تعیین اینکه کدام Default Method توسط کلاس خاصی استفاده شود، ارائه میدهد.
جاوا از ارثبری چندگانهی نوع (Multiple Inheritance of Type) پشتیبانی میکند — یعنی توانایی یک کلاس برای پیادهسازی بیش از یک Interface. یک شی میتواند چند نوع داشته باشد: نوع کلاس خودش و انواع تمام Interfaceهایی که کلاس پیادهسازی کرده است. این یعنی اگر متغیری به نوع یک Interface تعریف شده باشد، مقدار آن میتواند به هر شیای ارجاع دهد که از هر کلاسی نمونهسازی شده باشد که آن Interface را پیادهسازی میکند. این موضوع در بخش «استفاده از Interface به عنوان نوع» بیشتر بحث شده است.
مشابه ارثبری چندگانهی پیادهسازی، یک کلاس میتواند پیادهسازیهای مختلفی از یک متد که در Interfaceهایی که از آن ارث میبرد به صورت default یا static تعریف شده، به ارث ببرد. در این حالت، کامپایلر یا کاربر باید تصمیم بگیرد کدام پیادهسازی استفاده شود.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://dev.java/learn/