الـ Upcasting والـ Downcasting
جزء من قسم البرمجة كائنية التوجه في رحلة Java على Coddy. الدرس 30 من 87.
في الدرس السابق، رأيت كيف يمكن لمتغير من النوع parent أن يحتفظ بكائن من النوع الابن: Animal myPet = new Dog(). يُسمّى هذا الإسناد upcasting، ويحدث تلقائيًا لأن Dog يكون دائمًا من النوع Animal.
Upcasting ينقل إلى أعلى تسلسل الوراثة. وهو ضمني وآمن دائمًا:
Dog dog = new Dog();
Animal animal = dog; // Upcasting - تلقائيعند إجراء upcast، لا يمكنك الوصول إلا إلى methods المعرّفة في الصنف parent. تصبح methods الخاصة بـ child مخفية، رغم أن الكائن لا يزال Dog في جوهره.
يتم Downcasting في الاتجاه المعاكس، إذ يحوّل مرجعًا إلى parent مرة أخرى إلى نوع child. يتطلب ذلك عملية cast صريحة لأن هذا ليس آمنًا دائمًا:
Animal animal = new Dog(); // التحويل لأعلى (Upcasting)
Dog dog = (Dog) animal; // التحويل لأسفل - يتطلب تحويلاً صريحاً
dog.bark(); // يمكننا الآن الوصول إلى دوال Dogإن عملية Downcasting محفوفة بالمخاطر. إذا لم يكن الكائن في الواقع من النوع الذي تقوم بالتحويل إليه، فإن Java تطرح ClassCastException في وقت التشغيل:
Animal animal = new Cat();
Dog dog = (Dog) animal; // يتم التجميع، ولكنه يتعطل أثناء وقت التشغيل!لا يستطيع المُصرِّف اكتشاف هذا الخطأ لأنه لا يرى سوى الأنواع المُعلنة. لا يُعرف النوع الفعلي للكائن إلا عند تشغيل البرنامج. في الدرس التالي، ستتعلّم كيفية التحقّق بأمان من نوع كائن قبل إجراء Downcasting.
التحدي
سهللنَبْنِ نظامًا لمشغّل وسائط يوضّح عمليتَي upcasting وdowncasting عمليًا. ستنشئ تسلسلاً هرميًا لأنواع الوسائط، وتتدرّب على التحويل بين المراجع إلى parent وchild.
ستنظّم التعليمات البرمجية عبر أربعة ملفات:
Media.java: أنشئ الـ base class الذي تشترك فيه جميع أنواع الوسائط. يحتوي كل عنصر وسائط علىtitlefield من النوع String. أدرج constructor لتهيئته، وgetter method هيgetTitle()، وmethod هيplay()تطبع:Playing: [title]Song.java: أنشئ class يمتد من Media. تحتوي الأغاني علىartistfield إضافي من النوع String. استخدمsuperلتهيئة parent. نفّذ Override لـplay()لتطبع:Playing song: [title] by [artist]. أضف method هيshowArtist()تطبع:Artist: [artist]Podcast.java: أنشئ class أخرى تمتد من Media. تحتوي ملفات Podcast الصوتية علىepisodefield من النوع int. نفّذ Override لـplay()لتطبع:Playing podcast: [title] - Episode [episode]. أضف method هيshowEpisode()تطبع:Episode: [episode]Main.java: وضّح عمليتَي upcasting وdowncasting باستخدام أنواع الوسائط لديك. ستتلقى أربعة inputs: عنوان أغنية، واسم artist، وعنوان Podcast، ورقم episode.أولًا، أنشئ Song ونفّذ له upcast إلى مرجع Media. استدعِ
play()على هذا المرجع لرؤية polymorphism وقت التشغيل أثناء العمل.بعد ذلك، نفّذ downcast لمرجع Media هذا مجددًا إلى Song، واستدعِ
showArtist()للوصول إلى method الخاصة بالـ child.ثم أنشئ Podcast ونفّذ له upcast إلى مرجع Media. استدعِ
play()على هذا المرجع.أخيرًا، نفّذ downcast لمرجع Media هذا مجددًا إلى Podcast، واستدعِ
showEpisode().
ستتلقى أربعة inputs: عنوان الأغنية (String)، واسم artist (String)، وعنوان Podcast (String)، ورقم episode (int).
يجب أن يعرض output أربعة أسطر — سطران للأغنية (ناتج play ومعلومات artist) وسطران لـ Podcast (ناتج play ومعلومات episode). لاحظ كيف يتيح لك upcasting التعامل مع أنواع الوسائط المختلفة بطريقة موحّدة، بينما يتيح لك downcasting الوصول إلى ميزاتها الفريدة!
جرّب بنفسك
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// قراءة المدخلات
String songTitle = scanner.nextLine();
String artistName = scanner.nextLine();
String podcastTitle = scanner.nextLine();
int episodeNumber = scanner.nextInt();
// TODO: إنشاء كائن Song وإجراء upcast له إلى مرجع Media
// TODO: استدعاء play() على مرجع Media (يوضح مفهوم polymorphism)
// TODO: إجراء downcast لمرجع Media وإعادته إلى Song
// TODO: استدعاء showArtist() على مرجع Song
// TODO: إنشاء كائن Podcast وإجراء upcast له إلى مرجع Media
// TODO: استدعاء play() على مرجع Media
// TODO: إجراء downcast لمرجع Media وإعادته إلى Podcast
// TODO: استدعاء showEpisode() على مرجع Podcast
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1أساسيات الـ OOP
الملفات الخارجيةمقدمة في الـ OOPالفئات (Classes) مقابل الكائنات (Objects)الكلمة المفتاحية thisالدوال (Methods)الحقول (Attributes)دالة البناء (Constructor)تعدد أشكال دالة البناء (Constructor Overloading)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسية (extends)الكلمة المفتاحية superإعادة تعريف الدوال (@Override)تسلسل المنشئات (Constructor Chaining)كلاس Objectالوراثة الأحادية ومتعددة المستوياتلماذا لا توجد وراثة متعددة للكلاساتمراجعة - هيكلية الموظفين7الدوال الخاصة وفئة Object
دالة toString()equals() و hashCode()دالة clone()compareTo() و Comparableواجهة Comparatorمراجعة - الترتيب المخصص2محددات الوصول والتغليف
نظرة عامة على مستويات الوصولدوال Getter و Setterإخفاء البياناتالكلمة المفتاحية finalمراجعة - مدير الحساب البنكي5تعدد الأشكال (Polymorphism)
أساسيات الـ Method Overloadingالـ Method Overriding (في وقت التشغيل)الـ Upcasting والـ Downcastingمعامل الـ instanceofالفئات والدوال المجردة (Abstract)مراجعة - حاسبة الأشكال8مفاهيم OOP المتقدمة
الـ Composition مقابل الـ Inheritanceالـ Aggregation مقابل الـ Compositionالفئات الداخلية والمتداخلة والمجهولة (Inner, Nested & Anonymous)الـ Enums ودوالهاالـ Records (Java 16+)الـ Sealed Classes (Java 17+)11أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Builderنمط Observerنمط Strategy3خصائص الفئة والعضو الساكن
المتغيرات الخاصة بالكائن مقابل المتغيرات الساكنةالدوال الساكنةالكتل البرمجية الساكنةالثوابت (static final)ملخص - العداد والأدوات المساعدة6الواجهات (Interfaces) والأصناف المجردة (Abstract Classes)
مقدمة في الواجهات (Interfaces)تنفيذ الواجهات (Interfaces)تنفيذ واجهات متعددةالـ Default و Static في الواجهاتالأصناف المجردة مقابل الواجهاتالواجهات الوظيفية (Functional Interfaces)مراجعة - نظام الدفع9الأنواع العامة (Generics)
مقدمة في الأنواع العامةالأصناف العامة (Generic Classes)الدوال العامة (Generic Methods)معاملات الأنواع المقيدةالرموز البديلة (?, extends, super)مراجعة - الحاويات العامة12أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المكررتدرّب بنفسك: مترجم Java عبر الإنترنت