تعریف لغوی Polymorphism به اصلی در زیستشناسی اشاره دارد که طبق آن یک ارگانیسم یا گونه میتواند شکلها یا مراحل بسیار متفاوتی داشته باشد. این اصل را میتوان در برنامهنویسی شیگرا و زبانهایی مثل جاوا هم به کار برد. زیرکلاسهای یک کلاس میتوانند رفتارهای منحصربهفرد خود را تعریف کنند و در عین حال بخشی از قابلیتهای یکسان والد را به اشتراک بگذارند.
Polymorphism را میتوان با یک تغییر کوچک در کلاس Bicycle نشان داد. مثلاً متدی به نام printDescription() را میتوان به کلاس اضافه کرد که تمام دادههای فعلی ذخیرهشده در یک نمونه را نمایش میدهد.
public void printDescription(){
IO.println("\nBike is " + "in gear " + this.gear
+ " with a cadence of " + this.cadence +
" and travelling at a speed of " + this.speed + ". ");
}
برای نمایش ویژگیهای Polymorphic در زبان جاوا، کلاس Bicycle را با یک MountainBike و یک RoadBike گسترش میدهیم. برای MountainBike فیلدی برای Suspension اضافه میکنیم که مقداری از نوع String است و مشخص میکند آیا دوچرخه کمکفنر جلویی دارد (Front) یا کمکفنر جلو و عقب (Dual).
کلاس بهروزرسانیشده به این صورت است:
public class MountainBike extends Bicycle {
private String suspension;
public MountainBike(
int startCadence,
int startSpeed,
int startGear,
String suspensionType){
super(startCadence,
startSpeed,
startGear);
this.setSuspension(suspensionType);
}
public String getSuspension(){
return this.suspension;
}
public void setSuspension(String suspensionType) {
this.suspension = suspensionType;
}
public void printDescription() {
super.printDescription();
IO.println("The " + "MountainBike has a" +
getSuspension() + " suspension.");
}
}
متد printDescription() که بازنویسی شده را توجه کنید. علاوه بر اطلاعاتی که قبلاً ارائه میشد، دادههای اضافهای درباره Suspension به خروجی اضافه شده است.
سپس کلاس RoadBike را ایجاد کنید. چون دوچرخههای جادهای یا مسابقهای لاستیکهای باریکی دارند، فیلدی برای پیگیری عرض لاستیک اضافه میکنیم. کلاس RoadBike به این صورت است:
public class RoadBike extends Bicycle{
// In millimeters (mm)
private int tireWidth;
public RoadBike(int startCadence,
int startSpeed,
int startGear,
int newTireWidth){
super(startCadence,
startSpeed,
startGear);
this.setTireWidth(newTireWidth);
}
public int getTireWidth(){
return this.tireWidth;
}
public void setTireWidth(int newTireWidth){
this.tireWidth = newTireWidth;
}
public void printDescription(){
super.printDescription();
IO.println("The RoadBike" + " has " + getTireWidth() +
" MM tires.");
}
}
توجه کنید که باز هم، متد printDescription() بازنویسی شده است. این بار اطلاعاتی درباره عرض لاستیک نمایش داده میشود.
برای خلاصه، سه کلاس داریم: Bicycle، MountainBike و RoadBike. دو زیرکلاس متد printDescription() را بازنویسی کرده و اطلاعات منحصربهفرد خود را چاپ میکنند.
برنامه تستی که سه متغیر Bicycle ایجاد میکند. هر متغیر به یکی از سه کلاس دوچرخه اختصاص داده میشود. سپس هر متغیر چاپ میشود.
public class TestBikes {
public static void main(String[] args){
Bicycle bike01, bike02, bike03;
bike01 = new Bicycle(20, 10, 1);
bike02 = new MountainBike(20, 10, 5, "Dual");
bike03 = new RoadBike(40, 20, 8, 23);
bike01.printDescription();
bike02.printDescription();
bike03.printDescription();
}
}
خروجی برنامه تست به این صورت است:
Bike is in gear 1 with a cadence of 20 and travelling at a speed of 10.
Bike is in gear 5 with a cadence of 20 and travelling at a speed of 10.
The MountainBike has a Dual suspension.
Bike is in gear 8 with a cadence of 40 and travelling at a speed of 20.
The RoadBike has 23 MM tires.
ماشین مجازی جاوا (JVM) متد مناسب را بر اساس نوع شیای که متغیر به آن ارجاع میدهد فراخوانی میکند، نه متدی که توسط نوع خود متغیر تعریف شده است. به این رفتار فراخوانی مجازی متد (Virtual Method Invocation) میگویند که نمونهای از ویژگیهای مهم Polymorphism در زبان جاوا را به نمایش میگذارد.
[دیاگرام: نحوهی فراخوانی متدها در Virtual Method Invocation — JVM نوع شی را بررسی و متد صحیح را بسته به نوع واقعی شی انتخاب میکند]
---درون یک کلاس، فیلدی که همان نام فیلد موجود در فوقکلاس را داشته باشد، فیلد فوقکلاس را مخفی میکند، حتی اگر نوعهایشان متفاوت باشد. درون زیرکلاس نمیتوان به فیلد موجود در فوقکلاس با نام ساده ارجاع داد. در عوض باید از طریق super به آن دسترسی پیدا کرد که در بخش بعدی پوشش داده خواهد شد. به طور کلی، مخفی کردن فیلدها توصیه نمیشود چون خوانایی کد را دشوار میکند.
اگر متد شما یکی از متدهای فوقکلاس را بازنویسی کرده باشد، میتوانید متد بازنویسیشده را با استفاده از کلمهکلیدی super فراخوانی کنید. همچنین میتوانید از super برای ارجاع به فیلد مخفیشده استفاده کنید (هرچند مخفی کردن فیلدها توصیه نمیشود). کلاس Superclass زیر را در نظر بگیرید:
public class Superclass {
public void printMethod() {
IO.println("Printed in Superclass.");
}
}
زیرکلاس Subclass که printMethod() را بازنویسی میکند:
public class Subclass extends Superclass {
// overrides printMethod in Superclass
public void printMethod() {
super.printMethod();
IO.println("Printed in Subclass");
}
public static void main(String[] args) {
Subclass s = new Subclass();
s.printMethod();
}
}
درون Subclass، نام سادهی printMethod() به متد تعریفشده در Subclass ارجاع میدهد که متد موجود در Superclass را بازنویسی میکند. بنابراین، برای ارجاع به printMethod() به ارثرسیده از Superclass، Subclass باید از نام واجد (Qualified Name) استفاده کند و super را همانطور که نشان داده شد به کار ببرد. کامپایل و اجرای Subclass خروجی زیر را چاپ میکند:
Printed in Superclass.
Printed in Subclass
مثال زیر نحوهی استفاده از کلمهکلیدی super برای فراخوانی Constructor فوقکلاس را نشان میدهد. به یاد بیاورید از مثال Bicycle که MountainBike زیرکلاس Bicycle است. این Constructor زیرکلاس است که Constructor فوقکلاس را فراخوانی و سپس کد مقداردهی اولیهی خودش را اضافه میکند:
public MountainBike(int startHeight,
int startCadence,
int startSpeed,
int startGear) {
super(startCadence, startSpeed, startGear);
seatHeight = startHeight;
}
فراخوانی Constructor فوقکلاس باید اولین خط در Constructor زیرکلاس باشد تا جاوا ۲۵، جایی که میتوانید کدی قبل از این فراخوانی اجرا کنید.
سینتکس فراخوانی Constructor فوقکلاس به این صورت است:
super();
یا
super(parameter list);
با super() Constructor بدون پارامتر فوقکلاس فراخوانی میشود. با super(parameter list) Constructor فوقکلاس با لیست پارامترهای متناظر فراخوانی میشود.
نکته: اگر Constructor بهصورت صریح Constructor فوقکلاس را فراخوانی نکند، کامپایلر جاوا بهطور خودکار فراخوانی به Constructor بدون پارامتر فوقکلاس را درج میکند. اگر فوقکلاس Constructor بدون پارامتر نداشته باشد، خطای زمان کامپایل دریافت خواهید کرد. کلاس Object دارای چنین Constructorای است، بنابراین اگر
Objectتنها فوقکلاس باشد، مشکلی پیش نمیآید.
اگر Constructor زیرکلاس، Constructor فوقکلاس را بهصورت صریح یا ضمنی فراخوانی کند، ممکن است فکر کنید زنجیرهای کامل از Constructorها فراخوانی خواهد شد، تا Constructor Object در انتها. در واقع، همین اتفاق میافتد. به این پدیده زنجیرهی Constructor (Constructor Chaining) میگویند و باید در زنجیرههای طولانی ارثبری از کلاسها از آن آگاه باشید.
[دیاگرام: زنجیرهی فراخوانی Constructor از زیرکلاس MountainBike تا Bicycle و در نهایت Object]
---میتوانید برخی یا تمام متدهای یک کلاس را final تعریف کنید. از کلمهکلیدی final در اعلان متد استفاده میشود تا نشان دهد متد قابل بازنویسی توسط زیرکلاسها نیست. کلاس Object همین کار را انجام میدهد — تعدادی از متدهایش final هستند.
ممکن است بخواهید متدی را final کنید اگر پیادهسازی دارد که نباید تغییر کند و برای حالت سازگار (Consistent State) شی حیاتی است. مثلاً ممکن است بخواهید متد getFirstPlayer() در کلاس ChessAlgorithm زیر را final کنید:
class ChessAlgorithm {
enum ChessPlayer { WHITE, BLACK }
...
final ChessPlayer getFirstPlayer() {
return ChessPlayer.WHITE;
}
...
}
متدهایی که از Constructor فراخوانی میشوند معمولاً باید final تعریف شوند. اگر Constructor متد non-final را فراخوانی کند، زیرکلاس ممکن است آن را با نتایج غیرمنتظره یا نامطلوب بازتعریف کند.
توجه کنید که میتوانید یک کلاس کامل را هم final تعریف کنید. کلاسی که final تعریف شده باشد قابل ارثبری نیست. این ویژگی بهویژه مفید است، مثلاً هنگام ایجاد یک کلاس غیرقابل تغییر (Immutable) مثل کلاس String.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://dev.java/learn/