الوراثة في جافا
وراثة: تعني Inheritance في اللغة الإنجليزية. و لقد ذكرت هذه الكلمة عدة مرات في دروس سابقة.
في جافا, الكلاس يمكنه أن يرث من كلاس آخر, و بالتالي يحصل على الدوال و المتغيرات الموجودة في هذا الكلاس.
فكرة الوراثة في جافا بسيطة, لكن فائدتها قوية جداً. فمثلاً إذا كنت تريد إنشاء كلاس جديد و لاحظت أنه يوجد كلاس جاهز يحتوي على كودات قد تفيدك يمكنك استغلالها بدل كتابتها من الصفر, أي يمكنك جعل الكلاس الذي قمت بتعريفه يرث هذا الكلاس, و بعدها يمكنك إستخدام جميع المتغيرات و الدوال التي ورثها الكلاس الجديد من الكلاس الجاهز.
مفهومSuperclass و Subclass في جافا
الكلاس الذي يرث من كلاس آخر يسمى Subclass و يسمى أيضاً ( derived class, extended class أو child class ).
الكلاس الذي يورث محتوياته لكلاس آخر يسمى Superclass و يسمى أيضاً ( base class أو parent class ).
مثال على استخدام الوراثة في جافا
الآن لنفترض أننا قمنا بتعريف كلاس إسمه A
يحتوي على متغير إسمه x
و دالة إسمها printA()
.
بعدها قمنا بإنشاء كلاس جديد فارغ إسمه B
و قلنا أنه يرث من الكلاس A
. إذاً هذا يعني أن الكلاس B
أصبح يملك نسخة من جميع المتغيرات و الدوال الموجودة في الكلاس A
.
إنتبه
الـ Subclass يرث كل شيء موجود في الـ Superclass بشكل تلقائي ما عدا الـكونستركتور.
مع العلم أنه يمكن استدعاء كونستركتور الـ Superclass من الـ Subclass بواسطة الكلمة super
التي سنشرحها لاحقاً في هذا الدرس.
الكلمة extends
في جافا
الكلمة extends
تستخدم لجعل الكلاس يرث من كلاس آخر.
مكان وضع الكلمة extends في جافا
نضع الكلمة extends
بعد إسم الكلاس, ثم نضع بعدها إسم الكلاس الذي نريد الوراثة منه.
الكود التالي يعني أن الكلاس B
يرث من الكلاس A
.
مثال على استخدام الكلمة extends في جافا
class A { } class B extends A { }
إنتبه: في حال كنت تحاول الوراثة من كلاس غير موجود سيظهر لك الخطأ التالي: java.lang.ExceptionInInitializerError
مثال
الآن لنفترض أننا قمنا بتعريف كلاس إسمه A
يحتوي على متغير إسمه x
و دالة إسمها printA()
. بعدها قمنا بإنشاء كلاس جديد فارغ إسمه B
و قلنا أنه يرث من الكلاس A
. إذاً هذا يعني أن الكلاس B
أصبح يملك نسخة من جميع المتغيرات و الدوال الموجودة في الكلاس A
.
بعد إنشاء هذا الكلاس, سنقوم بإنشاء الكلاس Main
لتجربته.
مثال على الكلمة extends في جافا
public class A { public int x; public void printA() { System.out.println("I am from class A"); } }
// A يرث المتغيرات و الدوال الموجودة في الكلاس B هنا قلنا أن الكلاس public class B extends A { // A سيحتوي المتغيرات و الدوال الموجودة في الكلاس B إذاً أي كائن من الكلاس }
public class Main { public static void main(String[] args) { // أم لا A لنتأكد إذا كان يحتوي على الأشياء الموجودة في الكلاس B هنا قمنا بإنشاء كائن من الكلاس B b = new B(); // A من الكلاس B التي ورثها الكلاس printA() هنا قمنا باستدعاء الدالة b.printA(); // أيضاً, يمكننا إعطائه قيمة و عرض قيمته x يملك متغير إسمه b و بما أن الكائن b.x = 123; System.out.println("x: " +b.x); } }
•سنحصل على النتيجة التالية عند التشغيل.
I am from class A x: 123
شرح مفهوم الكلمةsuper
في جافا
الكلمة super
تستخدم للأهداف التالية:
للتمييز بين الأشياء (المتغيرات و الدوال) الموجودة في الـ Superclass و Subclass في حال كانت الأسماء مستخدمة في كلا الكلاسَين.
لإستدعاء الـكونستركتور الموجود في الـ Superclass.
إذاً الكلمة super
تستخدم لإستدعاء الأشياء الموجودة في الـ Superclass.
طريقة استخدام الكلمة super
لإستدعاء متغير من الـ Superclass
نضع الكلمة super
, بعدها نقطة, ثم نضع إسم المتغير الذي نريد إستدعائه من الـ Superclass.
طريقة استخدام الكلمة super
لإستدعاء دالة من الـ Superclass في جافا
نضع الكلمة super
, بعدها نقطة, ثم نضع إسم الدالة التي نريد إستدعائها من الـ Superclass.
إنتبه
في حال قام الـ Subclass بتعريف دالة كانت أصلاً موجودة في الـ Superclass يجب كتابة الكلمة @Override
قبلها مباشرةً, و هكذا سيفهم المترجم أن الـ Subclass قام بتعريف الدالة التي ورثها من الـ Superclass من جديد.
طريقة استخدامها عند استدعاء كونستركتور
يمكن استدعاء كونستركتور الـ Superclass من داخل كونستركتور الـ Subclass من خلال الكلمة super
.
// أو هكذا
super( parameter List ) // عند استدعاء كونستركتور يحتوي على باراميترات, عليك تمرير قيم له.
في حال كان الـ Superclass يملك فقط كونستركتور لا يحتوي أي باراميترات (أي مثل كونستركتور إفتراضي), سيقوم المترجم باستدعائه بشكل تلقائي في الـ Subclass حتى لو لم تقم باستدعائه بنفسك.
و في حال كان الـ Superclass يملك أكثر من كونستركتور, ستكون مجبر على تعريف كونستركتور في الـ Subclass يستدعي أي كونستركتور من الكونستركتورات الموجودة في الـ Superclass.
شرح أشكال الوراثة في جافا
في أي لغة برمجة, يوجد 3 أشكال أساسية للوراثة كما في الصورة التالية.
جافا لا تدعم تعدد الوراثة كما يوجد في بعض لغات البرمجة الأخرى, أي لا يمكن للكلاس الواحد الوراثة في نفس الوقت من أكثر من كلاس.
إذاً الكلاس الوحد لا يمكنه أن يفعل extends
لأكثر من كلاس , بمعنى أن كل كلاس يمكنه وراثة كلاس واحد.
جافا تدعم تعدد الوراثة من خلال interface
, أي من أجل الوراثة من أكثر من كلاس يجب إستخدام الكلمة interface
بدلاً من class
, و استخدام الكلمة implements
بدلاً من extends
ضمن شروط معينة. ستتعلم تعدد الوراثة في درس لاحق.
بالنسبة للوراثة المتتالية ( أو المتعددة المستويات ), دائماً آخر كلاس يرث جميع المتغيرات و الدوال الموجودة في الكلاسات الأعلى منه.
علاقة الكائنات مع بعضها في جافا
لنفترض أننا سنقوم بإنشاء برنامج لحفظ معلومات عن الحيوانات, يجب في البداية تجهيز كلاس أساسي يمثل جميع الخصائص المشتركة بين الحيوانات, بعدها يجب تقسيم الحيوانات إلى أربع فئات أساسية (ثدييات, زواحف, طيور, حيوانات مائية). بعدها يجب تعريف كل حيوان ضمن الفئة التي ينتمي لها.
الآن سنقوم بتمثيل المطلوب في الصورة بلغة جافا.
// هنا قمنا بتعريف الكلاس الأساسي لجميع الحيوانات class Animal { } // هنا قمنا بتعريف فئات الحيوانات class Mammals extends Animal { } class Reptiles extends Animal { } class Birds extends Animal { } class Aquatic extends Animal { } // هنا قمنا بتعريف 4 حيوانات مع وضع كل حيوان ضمن فئته class Dog extends Mammals { } class Snack extends Reptiles { } class Parrot extends Birds { } class Dolphin extends Aquatic { }
إذاً هنا يمكننا قراءة الكود كالتالي:
Dog
يعتبر من الـMammals
و يعتبرAnimal
أيضاً.Snack
يعتبر من الـReptiles
و يعتبرAnimal
أيضاً.Parrot
يعتبر من الـBirds
و يعتبرAnimal
أيضاً.Dolphin
يعتبر من الـAquatic
و يعتبرAnimal
أيضاً.
المقصود بالعاملinstanceof
في جافا
العامل instanceof
يستخدم لمعرفة إذا كان الكائن ينتمي إلى كلاس معين أم لا.
يرجع true
في حال كان الكائن ينتمي لكلاس معين, غير ذلك يرجع false
.
مثال
الآن سنقوم بكتابة الكود السابق و استخدام العامل instanceof
لمعرفة الكلاسات التي ينتمي إليها الكائن.
ملاحظة: هنا قمنا بتعريف جميع الكلاسات كـ private
وراء بعضهم في نفس ملف الجافا لأن جافا تسمح بذلك. لكننا لا ننصح باستخدام هذا الأسلوب في البرامج العادية.
كما أننا سنستخدم العامل instanceof
في دروس لاحقة.
مثال
// هنا قمنا بتعريف الكلاس الأساسي لجميع الحيوانات class Animal { } // هنا قمنا بتعريف فئات الحيوانات class Mammals extends Animal { } class Reptiles extends Animal { } class Birds extends Animal { } class Aquatic extends Animal { } // هنا قمنا بتعريف 4 حيوانات مع وضع كل حيوان ضمن فئته class Dog extends Mammals { } class Snack extends Reptiles { } class Parrot extends Birds { } class Dolphin extends Aquatic { } public class Main { public static void main(String[] args) { // Dog هنا قمنا بإنشاء كائن من الكلاس Dog dog1 = new Dog(); // dog1 هنا قمنا باختبار الكلاسات التي ينتمي إليها الكائن System.out.println( dog1 instanceof Dog); System.out.println( dog1 instanceof Animal); } }
•سنحصل على النتيجة التالية عند التشغيل.
true true
شرح الكلاسObject
في جافا
في جافا, يوجد مئات الكلاسات الجاهزة التي تم بناءها بشكل هرمي كما في الصورة التالية.
جميع الكلاسات في جافا ترث بشكل تلقائي من الكلاس Object
لأنه فعلياً يأتي في رأس الهرم و بالتالي فوق الجميع.
إذاً الكلاس Object
هو أعلى Superclass في جافا.
و إذا لاحظت في الدروس السابقة أن أي كلاس جديد كنا نستخدمه يحتوي على الدوال equals()
, hashcode()
, toString()
إلخ..
سبب وجود هذه الدوال في كل كلاس كنا نستخدمه أنه ورثوهم من الكلاس Object
.
مثال حول طريقة إستدعاء متغير من الـ Superclass
لنفترض أننا قمنا بتعريف كلاس إسمه A
يحتوي على متغير إسمه x
.
بعدها قمنا بإنشاء كلاس إسمه B
يرث من الكلاس A
و يحتوي على متغير إسمه x
, و دالة إسمها printBoth()
تطبع قيمة x
الموجود في A
و قيمة x
الموجود في B
بطريقتين.
بعدها سنقوم بإنشاء الكلاس Main
لتجربة الكود.
public class A { public int x = 5; }
public class B extends A { // A يرث من الكلاس B هنا قلنا أن الكلاس public int x = 20; // مع وضع قيمة مختلفة له A هنا قمنا بتعريف نفس المتغير الموجود في الكلاس public void printBoth() { // عند استدعاء هذه الدالة سيتم عرض الأشياء التالية System.out.println("x in B contain: " +x); // B الموجودة في الكلاس x هنا سيتم عرض قيمة الـ System.out.println("x in B contain: " +this.x); // B الموجودة في الكلاس x هنا سيتم عرض قيمة الـ System.out.println("x in A contain: " +super.x); // A الموجودة في الكلاس x هنا سيتم عرض قيمة الـ } }
public class Main { public static void main(String[] args) { B b = new B(); // منه printBoth() من أجل إستدعاء الدالة B هنا قمنا بإنشاء كائن من الكلاس b.printBoth(); // هنا قمنا باستدعائها } }
•سنحصل على النتيجة التالية عند التشغيل.
x in B contain: 20 x in B contain: 20 x in A contain: 5
مثال حول طريقة إستدعاء دالة من الـ Superclass
تذكر
لا تنسى وضع الكلمة @Override
قبل إسم الدالة التي ورثها الـ Subclass في الأصل من الـ Superclass و لكنه قام بتعريفها من جديد.
الآن لنفترض أننا قمنا بتعريف كلاس إسمه A
يحتوي على دالة إسمه print()
.
بعدها قمنا بإنشاء كلاس إسمه B
يرث من الكلاس A
و يحتوي على دالة إسمها print()
أيضاً, بالإضافة إلى دالة إسمها printBoth()
تستدعي الدالة print()
الموجودة في A
و تستدعي الدالة print()
الموجودة في B
بطريقتين.
بعدها سنقوم بإنشاء الكلاس Main
لتجربة الكود.
public class A { public void print() { System.out.println("This is print() method from the class A"); } }
public class B extends A { // A يرث من الكلاس B هنا قلنا أن الكلاس // قبلها, مع وضع جملة مختلفة في دالة الطباعة @Override لذلك وضعنا A هنا قمنا بتعريف نفس الدالة الموجودة في الكلاس @Override public void print() { System.out.println("This is print() method from the class B"); } // هنا قمنا بتعريف دالة مهمتها فقط إستدعاء الدوال الموجودة بداخلها public void printBoth() { print(); // B الموجودة في الكلاس print() هنا سيتم إستدعاء الدالة this.print(); // B الموجودة في الكلاس print() هنا سيتم إستدعاء الدالة super.print(); // A الموجودة في الكلاس print() هنا سيتم إستدعاء الدالة } }
public class Main { public static void main(String[] args) { B b = new B(); // منه printBoth() من أجل إستدعاء الدالة B هنا قمنا بإنشاء كائن من الكلاس b.printBoth(); // هنا قمنا باستدعائها } }
•سنحصل على النتيجة التالية عند التشغيل.
This is print() method from the class B This is print() method from the class B This is print() method from the class A
أمثلة حول طريقة إستدعاء كونستركتور لا يحتوي باراميترات من الـ Superclass
في المثال التالي قمنا بتعريف كلاس إسمه A
يحتوي على متغيرين x
و y
بالإضافة إلى كونستركتور لا يقبل أي باراميتر و مهمته بتوليد قيم لهما فقط.
بعدها قمنا بإنشاء كلاس إسمه B
يرث من الكلاس A
.
بعدها سنقوم بإنشاء الكلاس Main
لتجربة الكود.
المثال الأول
public class A { public int x; public int y; // A هنا قمنا بتعريف الكونستركتور الإفتراضي للكلاس // سيتم تنفيذه بشكل تلقائي في أي كلاس يرث منه .A و بما أنه لا يوجد غيره في الكلاس public A() { x = 50; y = 100; } }
public class B extends A { // A يرث من الكلاس B هنا قلنا أن الكلاس // أي عند إنشاء كائن منه ,B عند استدعاء الكونستركتور الإفتراضي للكلاس // حتى و إن لم نقم باستدعائه A سيتم إستدعاء الكونستركتور الإفتراضي الموجود في الكلاس }
public class Main { public static void main(String[] args) { // A من أجل عرض قيم المتغيرات التي ورثها من الكلاس B هنا قمنا بإنشاء كائن من الكلاس B b = new B(); System.out.println("x: " + b.x); System.out.println("y: " + b.y); } }
•سنحصل على النتيجة التالية عند التشغيل.
x: 50 y: 100
في المثال التالي قمنا بكتابة نفس الكود السابق مع إستدعاء كونستركتور الكلاس A
في كونستركتور الكلاس B
.
تذكر أنه لا يوجد داعي لكتابة هذا الكود لأن مترجم لغة جافا يفعل ذلك بشكل تلقائي عنك.
المثال الثاني
public class A { public int x; public int y; // A هنا قمنا بتعريف الكونستركتور الإفتراضي للكلاس // سيتم تنفيذه بشكل تلقائي في أي كلاس يرث منه .A و بما أنه لا يوجد غيره في الكلاس public A() { x = 50; y = 100; } }
public class B extends A { // A يرث من الكلاس B هنا قلنا أن الكلاس // نفس الشرح السابق تماماً, لكن هنا يمكنك القول أننا ذكرنا ما حدث بتفصيل public B() { // B عند استخدام هذا الكونستركتور لإنشاء كائن من الكلاس super(); // A سيتم إستدعاء كونستركتور الكلاس } }
public class Main { public static void main(String[] args) { // A من أجل عرض قيم المتغيرات التي ورثها من الكلاس B هنا قمنا بإنشاء كائن من الكلاس B b = new B(); System.out.println("x: " + b.x); System.out.println("y: " + b.y); } }
•سنحصل على النتيجة التالية عند التشغيل.
x: 50 y: 100
في المثال التالي قمنا بكتابة نفس الكود السابق مع تعريف متغير جديد في الكلاس B
إسمه z
و إعطائه قيمة في الكونستركتور, و تغيير قيمة المتغير x
في كونستركتور الكلاس B
.
عند تجربة الكود سنقوم بعرض قيم المتغيرات الموجودة في A
و B
.
الهدف الحقيقي هنا إيصال فكرة أن الـ Subclass يمكنه إستخدام الأشياء التي ورثها من الـ Superclass كما يشاء.
المثال الثالث
public class A { public int x; public int y; // A هنا قمنا بتعريف الكونستركتور الإفتراضي للكلاس // سيتم تنفيذه بشكل تلقائي في أي كلاس يرث منه .A و بما أنه لا يوجد غيره في الكلاس public A() { x = 50; y = 100; } }
public class B extends A { // A يرث من الكلاس B هنا قلنا أن الكلاس public int z; // z هنا قمنا بتعريف المتغير public B() { // B عند استخدام هذا الكونستركتور لإنشاء كائن من الكلاس super(); // A سيتم إستدعاء كونستركتور الكلاس z = 123; // z و سيتم إعطاء قيمة للمتغير x = 9; // A بالقيمة 9, مع الملاحظة أنها ستبقى 50 في أي كائن من الكلاس B في أي كائن من الكلاس x هنا سيتم تبديل قيمة المتغير } }
public class Main { public static void main(String[] args) { // A من أجل عرض قيم المتغيرات التي ورثها من الكلاس B هنا قمنا بإنشاء كائن من الكلاس B b = new B(); System.out.println("In class B we have:"); System.out.println("x: " + b.x); System.out.println("y: " + b.y); System.out.println("z: " + b.z); System.out.println(); // من أجل التأكد من أن قيم متغيراته لا تتأثر إذا تم تغيير نفس المتغيرات في أي كلاس يرث منه A هنا قمنا بإنشاء كائن من الكلاس A a = new A(); System.out.println("In class A we have:"); System.out.println("x: " + a.x); // B الموجود في الكلاس x مختلفة عن قيمة المتغير A الموجود في الكلاس x لاحظ كيف أن قيمة المتغير System.out.println("y: " + a.y); } }
•سنحصل على النتيجة التالية عند التشغيل.
In class B we have: x: 9 y: 100 z: 123 In class A we have: x: 50 y: 100
أمثلة حول وجود أكثر من كونستركتور في الـ Superclass
الآن سنقوم بتقسيم الأمثلة حسب الحالة التي قد نصادفها.
الحالة الأولى
في حال كان الـ Superclass يملك 2 كونستركتور, الأول لا يحتوي أي باراميترات و الثاني يحتوي باراميترات.
هنا يمكنك عدم تعريف كونستركتور في الـ Subclass, أو تعريف واحد فارغ لكي يستدعي الكونستركتور الإفتراضي الموجود في الـ Subclass.
في هذا المثال قمنا بتعريف كلاس إسمه A
يحتوي على متغيرين x
و y
بالإضافة إلى 2 كونستركتور, الأول لا يقبل أي باراميتر و مهمته بتوليد قيم لهما فقط و الثاني مهمته يتيح لك تمرير قيم للمتغيرات x
و y
مباشرةً عند إنشاء كائن.
بعدها قمنا بإنشاء كلاس إسمه B
يرث من الكلاس A
.
بعدها سنقوم بإنشاء الكلاس Main
لتجربة الكود.
المثال الأول
public class A { public int x; public int y; // عند استدعائه y و x هنا قمنا بتعريف كونستركتور لا يأخذ أي باراميتر و مهمته فقط إعطاء قيم أولية للمتغيرات public A() { x = 50; y = 100; } // عند استدعائه y و x هنا قمنا بتعريف كونستركتور يسمح بتمرير قيم أولية للمتغيرات public A(int x, int y) { this.x = x; this.y = y; } }
public class B extends A { // A يرث من الكلاس B هنا قلنا أن الكلاس // هنا لا داعي لتعريف كونستركتور جديد // حتى و إن لم نقم باستدعائه A هنا سيتم إستدعاء الكونستركتور الإفتراضي ( الذي لا يحتوي أي بارميتر ) الموجود في الكلاس }
public class Main { public static void main(String[] args) { // B باستخدام الكونستركتور الفارغ الموجود بشكل تلقائي في الكلاس B هنا قمنا بإنشاء كائن من الكلاس B b = new B(); System.out.println("x: " + b.x); System.out.println("y: " + b.y); } }
•سنحصل على النتيجة التالية عند التشغيل.
x: 50 y: 100
الحالة الثانية
في حال كان الـ Superclass يملك كونستركتور أو أكثر, لكنه لا يملك على كونستركتور لا يحتوي باراميترات.
هنا أنت مجبر على تعريف كونستركتور في الـ Subclass, يستدعي أي كونستركتور من الـ constructors الموجودين في الـ Superclass.
ملاحظة: عدد الباراميترات التي تضعها في كونستركتور الـ Subclass غير مهم حتى يعمل الكود بشكل سليم. المهم هنا استدعاء أي كونستركتور موجود في الـ Superclass بداخل أي كونستركتور ستقوم بتعريفه في الـ Subclass.
في هذا المثال قمنا بتعريف كلاس إسمه A
يحتوي على متغيرين x
و y
بالإضافة إلى 2 كونستركتور, الأول لا يقبل أي باراميتر و مهمته بتوليد قيم لهما فقط و الثاني مهمته يتيح لك تمرير قيم للمتغيرات x
و y
مباشرةً عند إنشاء كائن.
بعدها قمنا بإنشاء كلاس إسمه B
يرث من الكلاس A
.
بعدها سنقوم بإنشاء الكلاس Main
لتجربة الكود.
المثال الثاني
public class A { public int x; public int y; // عند استدعائه y و x يحتوي على كونستركتور يسمح بتمرير قيم أولية للمتغيرات A الكلاس // لأنه لا يوجد كونستركتور لا يحتوي على باراميترات Subclass هنا يجب استدعاء هذا الكونستركتور في أي public A(int x, int y) { this.x = x; this.y = y; } }
public class B extends A { // A يرث من الكلاس B هنا قلنا أن الكلاس // A هنا يجب تعريف كونستركتور واحد على الأقل يستدعي الكونستركتور الموجود في الكلاس // Superclass A هنا قمنا بتعريف كونستركتور يمرر القيمتين 123 و 456 في كونستركتور الـ public B() { super(123, 456); } // Superclass A هنا قمنا بتعريف كونستركتور نمرر له قيمتين عند استدعائه, فيقوم بدوره بتمريرهما في كونستركتور الـ public B(int p1, int p2) { super(p1, p2); } }
public class Main { public static void main(String[] args) { // الذي لا يأخذ باراميترات B باستخدام كونستركتور الكلاس B هنا قمنا بإنشاء كائن من الكلاس B b1 = new B(); System.out.println("Here the constructor generate these values for the object b1:"); System.out.println("x: " + b1.x); System.out.println("y: " + b1.y); System.out.println(); // الذي يأخذ 2 باراميتر B باستخدام كونستركتور الكلاس B هنا قمنا بإنشاء كائن من الكلاس B b2 = new B(47, 13); System.out.println("Here the constructor generate these values for the object b2:"); System.out.println("x: " + b2.x); System.out.println("y: " + b2.y); } }
•سنحصل على النتيجة التالية عند التشغيل.
Here the constructor generate these values for the object b1: x: 123 y: 456 Here the constructor generate these values for the object b2: x: 47 y: 13