الوراثة وتعدّد الأشكال

الرفع والخفض في الأنواع وعامل instanceof

15 دقيقة الدرس 6 من 14

الرفع والخفض في الأنواع وعامل instanceof

تتيح لك تعددية الأشكال (Polymorphism) كتابة كود يتعامل مع النوع الأب، بينما يكون الكائن الفعلي في وقت التشغيل من أي صنف فرعي. ولكي تفعل ذلك بصورة صحيحة، تحتاج إلى فهم عمليتين: الرفع في النوع (Upcasting) — أي التعامل مع كائن الصنف الفرعي باعتباره من النوع الأب — والخفض في النوع (Downcasting) — أي تحويله مجددًا إلى الصنف الفرعي المحدد. يُبقي عامل instanceof — وصيغة مطابقة الأنماط المُضافة في Java 16+ — هذه التحويلات آمنة.

الرفع في النوع — الاتجاه الآمن

يعني الرفع في النوع تخزين مرجع كائن من صنف فرعي في متغير من نوع الأب. تُجري Java هذا ضمنيًا لأنه آمن دائمًا: الـ Dog هو دائمًا Animal، ولن تضيع أي بيانات.

class Animal { String name; Animal(String name) { this.name = name; } void speak() { System.out.println(name + " makes a sound"); } } class Dog extends Animal { Dog(String name) { super(name); } @Override void speak() { System.out.println(name + " barks"); } void fetch() { System.out.println(name + " fetches the ball"); } } // الرفع — لا حاجة لصياغة تحويل صريحة Animal a = new Dog("Rex"); // رفع ضمني a.speak(); // تطبع: Rex barks (التوزيع الديناميكي يعمل)
النوع المُعلَن مقابل النوع في وقت التشغيل. بعد Animal a = new Dog("Rex")، النوع المُعلَن (وقت الترجمة) للمتغير a هو Animal، لكن النوع الفعلي في وقت التشغيل لا يزال Dog. تستجيب استدعاءات التوابع للنوع في وقت التشغيل (هذه هي تعددية الأشكال من الدرس السابق)، غير أن المترجم لا يسمح لك إلا بالاستدعاءات الموجودة في Animal. لذا فإن a.fetch() سيكون خطأ في وقت الترجمة — حتى لو كان الكائن فعلًا من نوع Dog.

الخفض في النوع — الاتجاه الصريح

يعني الخفض في النوع تحويل المرجع من النوع الأب مجددًا إلى نوع الصنف الفرعي المحدد. يجب أن تكتب التحويل صراحةً لأن المترجم لا يستطيع ضمان ذلك في وقت الترجمة. إذا أخطأت، رمى Java استثناء ClassCastException في وقت التشغيل.

Animal a = new Dog("Rex"); // رفع // الخفض — صياغة صريحة مطلوبة Dog d = (Dog) a; d.fetch(); // الآن يمكن استدعاء توابع Dog المحددة: Rex fetches the ball // هذا سيتعطل في وقت التشغيل: // Cat c = (Cat) a; // ClassCastException — a هو Dog وليس Cat
لا تُجرِ الخفض بشكل أعمى. تحقق دائمًا من نوع الكائن قبل التحويل. خفض النوع الخاطئ يُترجَم بنجاح لكنه يتعطل في وقت التشغيل بـ ClassCastException. استخدم instanceof لتأمين كل عملية خفض.

التحقق من الأنواع بـ instanceof

يختبر عامل instanceof ما إذا كان كائن ما نسخةً من صنف معين (أو أي من أصنافه الفرعية). استخدمه لتأمين الخفض:

Animal a = new Dog("Rex"); if (a instanceof Dog) { Dog d = (Dog) a; // آمن — نعرف أنه Dog d.fetch(); }

هذا النمط آمن لكنه مطوّل قليلًا: تتحقق ثم تُحوِّل وتنتهي بمتغير ثانٍ. قدّمت Java 16 طريقة أكثر إيجازًا.

مطابقة الأنماط لـ instanceof (Java 16+)

مع مطابقة الأنماط، يحدث التحقق والتحويل في تعبير واحد، وتربط Java النتيجة تلقائيًا بمتغير جديد:

Animal a = new Dog("Rex"); // الطريقة القديمة if (a instanceof Dog) { Dog d = (Dog) a; d.fetch(); } // طريقة مطابقة الأنماط (Java 16+) if (a instanceof Dog d) { d.fetch(); // d مُحدَّد النوع بـ Dog داخل هذه الكتلة }

المتغير d موجود فقط داخل كتلة if (وفقط حيث يمكن لـ Java إثبات نجاح الاختبار). يُزيل هذا فئةً كاملةً من أخطاء ClassCastException العَرَضية.

مثال عملي

تخيّل قائمة من كائنات Animal مختلطة. تريد استدعاء سلوك محدد لكل نوع:

import java.util.List; class Cat extends Animal { Cat(String name) { super(name); } @Override void speak() { System.out.println(name + " meows"); } void purr() { System.out.println(name + " purrs"); } } public class Main { public static void main(String[] args) { List<Animal> animals = List.of( new Dog("Rex"), new Cat("Whiskers"), new Dog("Buddy") ); for (Animal a : animals) { a.speak(); // متعدد الأشكال — يعمل دائمًا if (a instanceof Dog d) { d.fetch(); // خاص بـ Dog } else if (a instanceof Cat c) { c.purr(); // خاص بـ Cat } } } }
فضّل تعددية الأشكال على سلاسل instanceof. إذا وجدت نفسك تكتب سلاسل طويلة من if/else instanceof، فهذا غالبًا مؤشر على أن السلوك يجب أن يكون داخل تابع مُستبدَل. instanceof هو الأداة المناسبة حين تحتاج فعلًا إلى سلوك محدد بالنوع لا يمكن وضعه في الصنف الأب — مثلًا عند العمل مع أصناف طرف ثالث لا تستطيع تعديلها.

instanceof مع عمق التوارث

يُرجع instanceof قيمة true لصنف الكائن وكل أب في السلسلة الهرمية:

Dog rex = new Dog("Rex"); System.out.println(rex instanceof Dog); // true System.out.println(rex instanceof Animal); // true — Dog يمتد من Animal System.out.println(rex instanceof Object); // true — كل شيء يمتد من Object

الخلاصة

  • الرفع في النوع ضمني وآمن دائمًا — يمكن تخزين مرجع الصنف الفرعي في متغير من نوع الأب.
  • الخفض في النوع يتطلب تحويلًا صريحًا وقد يفشل في وقت التشغيل إذا كان الكائن من النوع الخاطئ.
  • استخدم instanceof لتأمين كل عملية خفض وتجنب ClassCastException.
  • تجمع مطابقة الأنماط لـ instanceof في Java 16+ بين الاختبار والتحويل في خطوة واحدة مع ربط متغير مكتوب النوع تلقائيًا.
  • يُرجع instanceof أيضًا true للأنواع الأصلية وليس للصنف المحدد فحسب.