مفهوم چندریختی
چند ریختی (polymorphism) در جاوا یعنی یک متغیر آبجکتی، به یک آبجکت از ساب کلاس های خودش (کلاس هایی که ازش ارث بری کرده اند) اشاره کنه.
سه اصل شی گرایی عبارتند از کپسوله سازی (Encapsulation)، وراثت (Inheritance) و چند ریختی (Polymorphism).
بعضیا Abstraction هم اصل چهارم شی گرایی میدونن که در اینجا موضوع قابل بحث ما نیست.
در مطالب قبل با دو اصل کپسوله سازی و وراثت اشنا شدیم؛ در این مطلب میخوایم به چند ریختی بپردازیم.
در قسمت مورد مطالعه ی مطلب قبلی یک مثال از کلاس های Circle، Rectangle و GeometricShape زده بودیم.
میتونیم بگیم هر دایره یک شکل هندسیه اما هر شکل هندسی قطعا دایره نیست؛ با این استدلال در مثال قسمت قبل یک متغیر از نوع GeometricShape میتونه به یک آبجکت از نوع Circle اشاره کنه.
حالا میخوایم یک متد عمومی درست کنیم تا اطلاعات هر کلاسی که از GeometricShape ارث میبره رو نمایش بده.
public static void main(String args){
displayShapeInfo(new Circle(4, "Blue", true);
displayShapeInfo(new Rectangle(7, 6, "Yellow", true));
}
public static void displayShapeInfo(GeometricShape shape){
System.out.println("Created on " + shape.getDateCreated() + "\nThe color is " + shape.getColor() + "\nIs it filled? " + shape.isFilled());
}
در بالا پارامتر متد displayShapeInfo از نوع GeometricShape است. این متغیر میتونه علاوه بر آبجکت هایی که از GeometricShape هستند به هر آبجکتی که از GeometricShape ارث بری میکنه اشاره کنه.
توجه
هنگامی که با یک متغیر از سوپرکلاس داریم به آدرس یک آبجکت از ساب کلاس اشاره میکنیم، فقط فیلد ها و متد های تعریف شده در سوپرکلاس رو میتونیم صدا بزنیم.
توجه
هر متغیر از نوع سوپرکلاس میتونه به یک آبجکت از ساب کلاس اشاره کنه اما برعکسش ممکن نیست به زبان ساده در مثال بخوایم بیان کنیم هر Circle یک GeometricShape است اما هر GeometricShape همیشه Circle نیست مثلا در بالا میتونه Rectangle باشه.
پیوندپویا (Dynamic Binding)
هنگامی که یک متد طی وراثت، در چند کلاس بازنویسی بشه JVM تصمیم میگیره متد در کدوم کلاس اجرا بشه.
یک متد طی وراثت میتونه در چند ساب کلاس پیادهسازی و بازنویسی بشه.
class A {
String myMethod(){
return "From A";
}
}
class B extends A {
@Override
String myMethod(){
return "From B";
}
}
class C extends B{
}
public static void main(String[] args){
A o = new C();
System.out.println(o.myMethod());
}
به نظر شما وقتی یک متغیر از نوع A اشاره کنه به آبجکتی از نوع C چطور باید بدونیم کدوم متد اجرا میشه؟
ابتدا به نوع بیان شده و نوع واقعی می پردازیم.
به متغیری که از نوع سوپر کلاس است نوع بیان شده (declared type) میگیم و به آبجکت اشاره شده از ساب کلاس، نوع واقعی (actual type) میگیم.
فرض کنید یک متغیر از نوع سوپرکلاس داره اشاره میکنه به یک آبجکت از ساب کلاس؛ وقتی یک متد که ابتدا در سوپرکلاس پیادهسازی شده و در ساب کلاس ها طی روند وراثت بازنویسی شده رو صدا میکنیم؛ JVM از actual type شروع میکنه به جستجو تا به declared type برسه و به اولین پیادهسازی متد قبل از declared type برسه اون متد رو به عنوان متد اجرایی در نظر میگیره و جستجو متوقف میشه.
در مثال بالا JVM از C که actual type است شروع میکنه به بررسی میبینه در C بازنویسی متد نداریم میره سراغ B و اونجا متد رو بازنویسی کردیم؛ متدی که در B بازنویسی کردیم رو به کار میگیره و دیگه سراغ بررسی A نمیره.
مثال
در مثال زیر متد toString رو در چند کلاس بازنویسی کردیم؛ زمان اجرا JVM از actual type شروع میکنه به جستجوی اولین پیادهسازی متد toString و بعد از پیدا کردن اولین پیادهسازی، متد رو اجرا میکنه و جستجو متوقف میشه.
public class DynamicBindingDemo {
public static void main(String[] args) {
Object graduateStudent = new GraduateStudent();
Object student = new Student();
Object person = new Person();
Object object = new Object();
System.out.println(graduateStudent.toString());
System.out.println(student.toString());
System.out.println(person.toString());
System.out.println(object.toString());
}
}
class GraduateStudent extends Student {
}
class Student extends Person {
@Override
public String toString() {
return "Student";
}
}
class Person extends Object {
@Override
public String toString() {
return "Person";
}
}
در مثال بالا سه متغیر از نوع Object به نمونه هایی از Student, Person و Object اشاره میکنه و در هر سه مورد فقط متد های toString، hashCode و equals رو با متغیر ها میتونیم صدا بزنیم چون در کلاس آبجکت فقط این متد ها تعریف شده.
کست کردن (Type Casting)
کست کردن یعنی تبدیل یک نوع به نوع دیگه؛ در جاوا دو نوع کست کردن پنهانی و آشکار داریم و برای هرکدوم قوانینی وجود داره.
کست کردن پنهانی
هنگامی که یک متغیر از نوع سوپرکلاس به یک آبجکت از ساب کلاس اشاره میکنه، تنها متد و فیلد های متغیر یا به عبارتی همون declared type در دسترس هستند و به این نوع تبدیل، کست کردن پنهانی (implicit casting) میگیم.
در کست کردن پنهانی نوع واقعی (actual type) باید ساب کلاس نوع بیان شده (declared type) باشه.
Object declared = new Student();
کست کردن آشکار
اگه acutal type ساب کلاس declared type باشه، میتونیم declared type رو به یکی از ساب کلاس هایی که در مسیر ارث بری acutal type قرار دارند تبدیل کنیم به این مدل کست کردن، کست کردن آشکار (explicit casting) میگیم.
Object declared = new Person();
Student student = (Student) declared;
Person person = (Person) declared;
کلیدواژه ی instanceof
کلیدواژه ی instanceof بررسی میکنه مقدار واقعی (actual type) یک متغیر، چه کلاسی است.
هنگام کست کردن سوپرکلاس به ساب کلاس (explicit casting) اگه متغیری که از نوع سوپرکلاس است به یک آبجکت از ساب کلاس اشاره نکنه زمان اجرا دچار ClassCastException میشیم. در جاوا یک کلیدواژه به نام instanceof وجود داره که قبل از کست کردن میتونیم بررسی کنیم ایا متغیر یک نمونه از ساب کلاس است یا خیر.
GeometricShape o = new Circle();
System.out.println("Is o instance of Circle? "+ o instanceof Circle);
System.out.println("Is o instance of Rectangle? " + o instanceof Rectangle);
مثال:
میتونیم در مورد مطالعه ی مطلب قبل، متد equals رو که ابتدا در کلاس Object تعریف شده در Circle و Rectangle بازنویسی کنیم و داخل متد از instanceof استفاده کنیم.
class GeometricShape {
...
}
class Circle extends GeometricShape{
...
@Override
public boolean equals(Object obj) {
if (obj instanceof Circle)
return ((Circle) obj).radius == this.radius;
return false;
}
}
class Rectangle extends GeometricShape{
...
@Override
public boolean equals(Object obj) {
if (obj instanceof Rectangle){
Rectangle r = (Rectangle) obj;
return r.width == this.width && r.height == this.height;
}
return false;
}
خلاصه
- سه رکن شی گرایی عبارتند از کپسوله سازی، وراثت و چند ریختی.
- چند ریختی یعنی یک متغیر از نوع سوپرکلاس اشاره کنه به آدرس آبجکت ایجاد شده از ساب کلاس.
- اگه متغیر از نوع سوپر کلاس باشه و به یک آبجکت از نوع ساب کلاس اشاره کنه به متغیر مقدار بیان شده (declared type) و به آبجکتی که بهش اشاره میکنه مقدار واقعی (actual type) میگیم.
- کست کردن یعنی تبدیل نوعی به نوع دیگه.
- با instanceof میتونیم نوع مقدار واقعی یک متغیر رو بررسی کنیم.