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

الكلمة المحجوزة super وتسلسل المنشئات

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

الكلمة المحجوزة super وتسلسل المنشئات

في الدرس السابق تعلّمت أن الفئة الابن تستخدم extends للوراثة من الفئة الأب. لكن ماذا يحدث حين يحتاج كلٌّ من الأب والابن إلى التهيئة؟ هنا يأتي دور الكلمة المحجوزة super. تتيح لك super العبور عبر حدود الوراثة والتواصل مباشرةً مع الأب — باستدعاء منشئه أو استدعاء إحدى دواله.

لماذا تهمّنا منشئات الأب؟

يجب أن يكون كل كائن في Java مهيَّأً تهيئةً كاملة قبل استخدامه. حين تُنشئ كائنًا من الفئة الابن، تتواجد حقول الفئة الأب داخل ذلك الكائن أيضًا، لذا يجب أن يعمل منشئ الأب أولًا لتهيئتها. تفرض Java ذلك تلقائيًا، لكنّك تحتاج إلى فهم القواعد لكتابة كود صحيح.

استدعاء منشئ الأب باستخدام super()

استخدم super(...) كأوّل عبارة داخل منشئ الابن لتمرير الوسائط إلى منشئ الأب.

// الفئة الأب class Animal { private String name; private int age; public Animal(String name, int age) { this.name = name; this.age = age; System.out.println("منشئ Animal: " + name + ", العمر " + age); } public String getName() { return name; } public int getAge() { return age; } } // الفئة الابن class Dog extends Animal { private String breed; public Dog(String name, int age, String breed) { super(name, age); // يجب أن تكون أولًا — تستدعي Animal(String, int) this.breed = breed; System.out.println("منشئ Dog: " + breed); } public String getBreed() { return breed; } } // نقطة البداية public class Main { public static void main(String[] args) { Dog d = new Dog("Rex", 3, "Labrador"); System.out.println(d.getName() + " سلالته " + d.getBreed()); } }

الناتج:

منشئ Animal: Rex, العمر 3 منشئ Dog: Labrador Rex سلالته Labrador

لاحظ الترتيب: منشئ الأب يعمل دائمًا قبل جسم الابن. هذا يضمن أن حقول الأب جاهزة بمجرّد أن يواصل منشئ الابن تنفيذه.

يجب أن تكون super() أوّل عبارة. وضعها في أي مكان آخر خطأ وقت الترجمة. إن نسيتها، تُدرج Java ضمنيًا super() (النسخة بدون وسائط) — لكن فقط إن كان للأب منشئ بدون وسائط. إن كان الأب يتطلّب وسائط ونسيت super(...)، لن يُجمَّع الكود.

ماذا لو لم تكتب super() صراحةً؟

حين يملك الأب منشئًا بدون وسائط، تُضيف Java نداء super() ضمنيًا تلقائيًا:

class Vehicle { public Vehicle() { System.out.println("تم إنشاء Vehicle"); } } class Car extends Vehicle { public Car() { // Java تُدرج: super(); تلقائيًا System.out.println("تم إنشاء Car"); } }

تشغيل new Car() يطبع "تم إنشاء Vehicle" أولًا ثم "تم إنشاء Car". النداء الضمني يتّبع نفس القاعدة — الأب يعمل أولًا.

استدعاء دالة الأب باستخدام super.method()

super ليست للمنشئات فقط. يمكنك استخدامها داخل دالة لاستدعاء نسخة الأب من تلك الدالة. هذا مفيد بشكل خاص حين تُلغي دالةً لكنّك تريد إعادة استخدام منطق الأب:

class Shape { public String describe() { return "أنا شكل هندسي"; } } class Circle extends Shape { private double radius; public Circle(double radius) { this.radius = radius; } @Override public String describe() { // أعد استخدام رسالة الأب ثم أضف تفصيلك return super.describe() + " — تحديدًا دائرة نصف قطرها " + radius; } } // نقطة البداية public class Main { public static void main(String[] args) { Circle c = new Circle(5.0); System.out.println(c.describe()); } }

الناتج:

أنا شكل هندسي — تحديدًا دائرة نصف قطرها 5.0
super.method() مقابل super() — لا تخلط بينهما. super() (بأقواس كعبارة مستقلة) تستدعي منشئ الأب ويجب أن تكون السطر الأوّل. super.someMethod() تستدعي دالة الأب ويمكن أن تظهر في أي مكان داخل جسم دالة الابن.

ترتيب تنفيذ المنشئات في تسلسل هرمي أعمق

تعمل السلسلة بنفس الطريقة بغضّ النظر عن عدد مستويات الوراثة. كل مستوى يستدعي super() أولًا، فيسير التنفيذ من أعلى الهرم إلى أسفله:

class A { public A() { System.out.println("A"); } } class B extends A { public B() { System.out.println("B"); } } class C extends B { public C() { System.out.println("C"); } } // new C() يطبع: // A // B // C

تبدأ Java دائمًا من الجدّ الأعلى وتنحدر إلى الأسفل. يمكنك الاعتماد على هذا الترتيب: بحلول وقت تنفيذ جسم منشئك، يكون كل أب فوقك قد انتهى من تهيئته.

كل فئة ترث من Object في نهاية المطاف. تقبع java.lang.Object على رأس كل هرم وراثة. منشئها بدون وسائط هو أوّل شيء يُستدعى دائمًا حتى لو لم تكتب super() أبدًا — وهذا هو السبب في أنك تستطيع استدعاء دوال مثل toString() وequals() على أي كائن Java دون استيراد أي شيء.

مثال عملي: هرم الموظفين

class Employee { private String name; private double baseSalary; public Employee(String name, double baseSalary) { this.name = name; this.baseSalary = baseSalary; } public double calculatePay() { return baseSalary; } public String getName() { return name; } } class Manager extends Employee { private double bonus; public Manager(String name, double baseSalary, double bonus) { super(name, baseSalary); // تهيئة الجزء الخاص بـ Employee this.bonus = bonus; } @Override public double calculatePay() { return super.calculatePay() + bonus; // أعد استخدام منطق الأب } } // نقطة البداية public class Main { public static void main(String[] args) { Manager m = new Manager("Alice", 5000, 1500); System.out.println(m.getName() + " راتبه " + m.calculatePay()); } }

الناتج: Alice راتبه 6500.0

هنا super(name, baseSalary) ترسل البيانات المشتركة إلى Employee، وsuper.calculatePay() تُعيد استخدام منطق الراتب الأساسي حتى لا يكرّر Manager ذلك المنطق.

الخلاصة

  • super(args) تستدعي منشئ الأب — يجب أن تكون أوّل سطر في أي منشئ ابن.
  • إن حذفتها، تُدرج Java ضمنيًا super() بدون وسائط؛ إن لم يكن للأب منشئ كذلك فسيحدث خطأ وقت الترجمة.
  • super.method() تستدعي نسخة الأب من دالة ويمكن أن تظهر في أي مكان داخل دالة الابن.
  • تُنفَّذ المنشئات دائمًا من أعلى الهرم إلى أسفله، فحقول الأب تكون جاهزة دائمًا قبل تشغيل كود الابن.