الـ Upcasting والـ Downcasting
جزء من قسم Object Oriented Programming في رحلة Java على Coddy — الدرس 30 من 87.
في الدرس السابق، رأيت كيف يمكن لمتغير من نوع الأب (parent-type) أن يحمل كائناً من نوع الابن (child object): Animal myPet = new Dog(). تُسمى عملية التعيين هذه upcasting، وهي تحدث تلقائياً لأن الـ Dog هو دائماً Animal.
Upcasting ينتقل للأعلى في تسلسل الوراثة الهرمي. إنه ضمني وآمن دائماً:
Dog dog = new Dog();
Animal animal = dog; // Upcasting - تلقائيعندما تقوم بالتحويل للأعلى (upcast)، يمكنك فقط الوصول إلى الدوال (methods) المعرفة في الفئة الأب (parent class). تصبح الدوال الخاصة بالفئة الابن مخفية، على الرغم من أن الكائن لا يزال Dog في جوهره.
يسير Downcasting في الاتجاه المعاكس، حيث يقوم بتحويل مرجع من النوع الأب (parent) مرة أخرى إلى نوع الابن (child). يتطلب هذا عملية تحويل صريحة (explicit cast) لأنه ليس آمناً دائماً:
Animal animal = new Dog(); // تحويل للأعلى (Upcasting)
Dog dog = (Dog) animal; // تحويل للأسفل (Downcasting) - يتطلب تحويلاً صريحاً
dog.bark(); // الآن يمكننا الوصول إلى دوال Dogعملية التحويل السفلي (Downcasting) محفوفة بالمخاطر. إذا لم يكن الكائن في الواقع من النوع الذي تقوم بالتحويل إليه، ستقوم Java بطرح ClassCastException في وقت التشغيل:
Animal animal = new Cat();
Dog dog = (Dog) animal; // يتم تجميعه، ولكنه ينهار عند التشغيل!لا يستطيع المترجم (compiler) اكتشاف هذا الخطأ لأنه يرى فقط الأنواع المعلنة. أما نوع الكائن الفعلي فلا يُعرف إلا عند تشغيل البرنامج. في الدرس القادم، ستتعلم كيفية التحقق بأمان من نوع الكائن قبل إجراء عملية التحويل السفلي (downcasting).
التحدي
سهللنقم ببناء نظام مشغل وسائط يوضح كلاً من الـ upcasting والـ downcasting بشكل عملي. ستقوم بإنشاء تسلسل هرمي لأنواع الوسائط وتتدرب على التحويل بين مراجع الأب والابن.
ستقوم بتنظيم الكود الخاص بك عبر أربعة ملفات:
Media.java: قم بإنشاء الفئة الأساسية (base class) التي تشترك فيها جميع أنواع الوسائط. كل عنصر وسائط لديه حقلtitleمن نوع (String). قم بتضمين constructor لتهيئته، وطريقة getter تسمىgetTitle()، وطريقةplay()تطبع:"Playing: " + titleSong.java: قم بإنشاء فئة ترث من Media. تحتوي الأغاني على حقل إضافيartistمن نوع (String). استخدمsuperلتهيئة الأب. قم بعمل override لطريقةplay()لتطبع:"Playing song: " + title + " by " + artist. أضف طريقةshowArtist()تطبع:"Artist: " + artistPodcast.java: قم بإنشاء فئة أخرى ترث من Media. تحتوي البودكاست على حقلepisodeمن نوع (int). قم بعمل override لطريقةplay()لتطبع:"Playing podcast: " + title + " - Episode " + episode. أضف طريقةshowEpisode()تطبع:"Episode: " + episodeMain.java: قم بتوضيح الـ upcasting والـ downcasting مع أنواع الوسائط الخاصة بك. ستتلقى أربعة مدخلات: عنوان الأغنية، اسم الفنان، عنوان البودكاست، ورقم الحلقة.أولاً، قم بإنشاء Song وقم بعمل upcast لها إلى مرجع Media. استدعِ
play()على هذا المرجع لرؤية تعدد الأشكال (polymorphism) في وقت التشغيل.بعد ذلك، قم بعمل downcast لمرجع Media هذا مرة أخرى إلى Song واستدعِ
showArtist()للوصول إلى الطريقة الخاصة بالابن.ثم، قم بإنشاء Podcast وقم بعمل upcast له إلى مرجع Media. استدعِ
play()على هذا المرجع.أخيراً، قم بعمل downcast لمرجع Media هذا مرة أخرى إلى Podcast واستدعِ
showEpisode().
ستتلقى أربعة مدخلات: عنوان الأغنية (String)، اسم الفنان (String)، عنوان البودكاست (String)، ورقم الحلقة (int).
يجب أن يظهر مخرجك أربعة أسطر - سطران للأغنية (مخرج التشغيل ومعلومات الفنان) وسطران للبودكاست (مخرج التشغيل ومعلومات الحلقة). لاحظ كيف يسمح لك الـ upcasting بالتعامل مع أنواع الوسائط المختلفة بشكل موحد، بينما يسمح لك الـ downcasting بالوصول إلى ميزاتها الفريدة!
ورقة مرجعية
في لغة Java، ينتقل التحويل للأعلى (upcasting) إلى أعلى في تسلسل الوراثة ويحدث تلقائيًا. يمكن تعيين كائن من النوع الابن (child) إلى متغير من النوع الأب (parent):
Dog dog = new Dog();
Animal animal = dog; // Upcasting - تلقائيعند إجراء التحويل للأعلى، يمكنك فقط الوصول إلى الأساليب (methods) المعرفة في الفئة الأب (parent class)، على الرغم من أن الكائن لا يزال من النوع الابن في الأصل.
يقوم التحويل للأسفل (downcasting) بتحويل مرجع الأب مرة أخرى إلى نوع الابن. يتطلب ذلك تحويلاً صريحًا (explicit cast):
Animal animal = new Dog(); // Upcasting
Dog dog = (Dog) animal; // Downcasting - تحويل صريح مطلوب
dog.bark(); // الآن يمكننا الوصول إلى أساليب Dogالتحويل للأسفل محفوف بالمخاطر. إذا لم يكن الكائن في الواقع من النوع الذي تقوم بالتحويل إليه، فإن Java ستطرح استثناءً من نوع ClassCastException في وقت التشغيل:
Animal animal = new Cat();
Dog dog = (Dog) animal; // يتم التجميع بنجاح، لكنه ينهار في وقت التشغيل!لا يمكن للمترجم (compiler) اكتشاف هذا الخطأ لأنه يرى فقط الأنواع المعلنة. لا يُعرف نوع الكائن الفعلي إلا عند تشغيل البرنامج.
جرّب بنفسك
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
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
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أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المكرر