مفهوم الـ Overriding
في الدرس السابق, شاهدت كيف أن الـ Subclass يرث المتغيرات و الدوال الموجودة في الـ Superclass. و تعلمت أيضاً أنه يمكن للـ Subclass إعادة تعريف أي دالة ورثها من الـ Superclass شرط أن لا تكون معرفة كـ final, و كتابة الكلمة @Override قبل تعريفها من جديد حتى تتفادى حدوث مشاكل عند ترجمة الكود.
Override: تعني تعريف الدالة التي ورثها الـ Subclass من الـ Superclass من جديد, هذه الدالة الجديدة تكون مشابهة للدالة الموروثة من حيث الشكل فقط, أي لها نفس الإسم و النوع و عدد الباراميترات, لكن محتواها مختلف.
الهدف الحقيقي من الـ Overriding هو إتاحة الفرصة للـ Subclass ليعرف الدوال حسب حاجته.
في دروس متقدمة سنرث من كلاسات جاهزة في جافا, و نفعل Override للدوال الموجودة فيها لكي تناسب التطبيقات التي سنقوم ببنائها.
مثال
الآن لنفترض أننا قمنا بتعريف كلاس إسمه Country, يحتوي على دالة إسمها language().
بعدها قمنا بتعريف ثلاث كلاسات, و كلها ترث من Country, إذاً كلها ستحتوي على الدالة language().
هنا الفكرة أن أي كلاس يرث من Country قد يضطر إلى تعريف الدالة language() من جديد حتى تناسبه.
بعد إنشاء هذه الكلاسات, سنقوم بإنشاء الكلاس Main لتجربتهم.
public class Country { // هنا, هذا الكلاس يعتبر الكلاس الأساسي لأي دولة في العالم, إذاً يجب أن يرثه أي كلاس يمثل دولة
public void language() {
System.out.println("English"); // هنا قمنا بوضع اللغة الإنجليزية كلغة إفتراضية لجميع البلدان
}
}
public class Australia extends Country {
// من جديد لأن اللغة الإنجليزية هي لغة أستراليا language() هنا لا داعي لتعريف الدالة
}
public class Lebanon extends Country {
// من جديد لأن اللغة الإنجليزية ليست لغة لبنان language() هنا يجب تعريف الدالة
@Override
public void language() {
System.out.println("Arabic");
}
}
public class Spain extends Country {
// من جديد لأن اللغة الإنجليزية ليست لغة إسبانيا language() هنا يجب تعريف الدالة
@Override
public void language() {
System.out.println("Spanish");
}
}
public class Main {
public static void main(String[] args) {
// هنا قمنا بإنشاء كائنات من البلدان الثلاثة
Australia au = new Australia();
Lebanon lb = new Lebanon();
Spain sp = new Spain();
// لعرض لغة كل بلد language() هنا قمنا باستدعاء الدالة
au.language();
lb.language();
sp.language();
}
}
•سنحصل على النتيجة التالية عند التشغيل.
English Arabic Spanish
أنت الآن فهمت لما قد تحتاج أن تفعل Override. سنقوم الآن بشرح طريقة عمل مترجم جافا عندما فعلت Override للدالة language().
إذا عدت للكلاس Lebanon, ستجد أننا فعلنا Override للدالة language(). إذاً هنا أصبح الكلاس Lebanon يملك دالتين إٍسمهما language(), الأولى هي التي ورثها من الـ Superclass, و الثانية هي التي قمنا بتعريفها عندما فعلنا Override.
بما أن الكلاس Lebanon يملك دالتين لهما نفس الإسم, النوع و عدد البارامتيرات, كيف عرف أي دالة يختار؟
عندما قمنا بتعريف الدالة language() من جديد في الـ Subclass, قام المترجم بإخفاء الدالة الأصلية ( أي دالة الـ Superclass ) و أظهر الدالة الجديدة فقط. بالإضافة إلى أن الكلمة @Override ضمنت لنا حصول هذا, لأنها تخبر المترجم أنه يوجد عدة دوال إسمهم language() لكننا نريد هذه الدالة بالتحديد عند استدعائها من هذا الكلاس.
إنتبه: في داخل الكلاس Lebanon, إذا أردنا إستخدام الدالة language() الموجودة في الـ Superclass و التي قام المترجم بإخفائها عندما قمنا بتعريفها من جديد, يمكننا ذلك بواسطة الكلمة super التي شرحناها في الدرس السابق.
شروط الـ Overriding للدوال
يجب أن يكون الـ Modifier المستخدم للدالة الجديدة هو نفسه المستخدم للدالة القديمة, و يجب أن يكون نوعه
publicأوprotected.عدد و نوع باراميترات الدالة الجديدة يجب أن يطابق عدد و نوع باراميترات الدالة القديمة.
نوع الإرجاع للدالة الجديدة يجب أن يكون نفس نوع الإرجاع للدالة القديمة.
الدالة المعرفة كـ
privateلا يمكن أن نفعل لها Override, لأن كلمةprivateتمنع إمكانية الوصول المباشر للدالة من الـ Subclass.الدالة المعرفة كـ
finalلا يمكن أن نفعل لها Override, لأن كلمةfinalتمنع تغير محتوى الدالة بعد تعريفها.الدالة المعرفة كـ
staticلا يمكن أن نفعل لها Override و لكن يمكن تعريفها من جديد في أي مكان, لأن كلمةstaticتجعل الدالة مشتركة بين جميع الكلاسات.لا يمكن أن نفعل Override للكونستركتور.
مفهوم الـ Overloading
Overloading: تعني تعريف أكثر من دالة أو كونستركتور, لهم نفس الإسم, مختلفون في عدد أو نوع الباراميترات.
الفكرة من الـ Overloading, هي تجهيز عدة دوال لهم نفس الإسم, هذه الدوال تكون متشابهة من حيث الوظيفة, مختلفة قليلاً في الأداء.
فعلياً, تكون كل دالة تحتوي على ميزات إضافية عن الدالة التي أنشأت قبلها.
شروط الـ Overloading
الـ Overloading يطبق فقط على الدوال و الـ Constructors.
يجب أن يملكوا نفس الإسم.
يجب أن يختلفوا في نوع أو عدد الباراميترات.
نوع الإرجاع غير مهم, لا يستطيع المترجم التفريق بين الدوال إذا كانوا مختلفين في نوع الإرجاع.
أمثلة شاملة حول الـ Overloading
المثال الأول
تعريف دوال لها نفس الإسم و تختلف في نوع الباراميترات.
المثال الثاني
تعريف دوال لها نفس الإسم و تختلف في عدد الباراميترات.
المثال الثالث
تعريف دوال تعتمد على دوال موجودة قبلها في نفس الكلاس.
المثال الرابع
تعريف دوال تعتمد على دوال موجودة في الـ Superclass.
المثال الخامس
تعريف أكثر من كونستركتور في الكلاس.
تعريف دوال لها نفس الإسم و تختلف في نوع الباراميترات في جافا
لنفترض أننا قمنا بتعريف كلاس إسمه MyMethods, يحتوي على ثلاث دوال إسمهم sum() و نوعهم public void.
الدالة الأولى مهمتها جمع أي عددين نوعهما int ثم طباعة قيمتهم.
الدالة الثانية مهمتها جمع أي عددين نوعهما float ثم طباعة قيمتهم.
الدالة الثالثة مهمتها جمع أي عددين نوعهما double ثم طباعة قيمتهم.
بعد إنشاء هذا الكلاس, سنقوم بإنشاء الكلاس Main و إستدعاء الدوال الثلاثة فيه.
مثال
public class MyMethods {
public void sum(int a, int b) {
System.out.println("First method is called ====> "+a+" + "+b+" = "+(a+b));
}
public void sum(float a, float b) {
System.out.println("Second method is called ===> "+a+" + "+b+" = "+(a+b));
}
public void sum(double a, double b) {
System.out.println("Third method is called ====> "+a+" + "+b+" = "+(a+b));
}
}
public class Main {
public static void main(String[] args) {
MyMethods m = new MyMethods(); // لإستدعاء الدوال منه MyMethods هنا قمنا بإنشاء كائن من الكلاس
m.sum(1000, 4000); // int هنا سيتم إستدعاء الدالة التي تأخذ 2 باراميتر نوعهم
m.sum(10f, 40f); // float هنا سيتم إستدعاء الدالة التي تأخذ 2 باراميتر نوعهم
m.sum(10d, 40d); // double هنا سيتم إستدعاء الدالة التي تأخذ 2 باراميتر نوعهم
System.out.println();
m.sum(2000, -100); // int هنا سيتم إستدعاء الدالة التي تأخذ 2 باراميتر نوعهم
m.sum(5.5, -3.3); // لأن الأرقام تحتوي على فاصلة double هنا سيتم إستدعاء الدالة التي تأخذ 2 باراميتر نوعهم
m.sum(5.5d, -3.3d); // double هنا سيتم إستدعاء الدالة التي تأخذ 2 باراميتر نوعهم
m.sum(5.5f, -3.3f); // بعد كل رقم f لأننا وضعنا float هنا سيتم إستدعاء الدالة التي تأخذ 2 باراميتر نوعهم
System.out.println();
m.sum(6, 5.25); // يقبل كلا النوعين double لأن النوع double هنا سيتم إستدعاء الدالة التي تأخذ 2 باراميتر نوعهم
m.sum(6, 5.25f); // يقبل كلا النوعين float لأن النوع float هنا سيتم إستدعاء الدالة التي تأخذ 2 باراميتر نوعهم
m.sum(6, 5.25d); // يقبل كلا النوعين double لأن النوع double هنا سيتم إستدعاء الدالة التي تأخذ 2 باراميتر نوعهم
}
}
•سنحصل على النتيجة التالية عند التشغيل.
First method is called ====> 1000 + 4000 = 5000 Second method is called ===> 10.0 + 40.0 = 50.0 Third method is called ====> 10.0 + 40.0 = 50.0 First method is called ====> 2000 + -100 = 1900 Third method is called ====> 5.5 + -3.3 = 2.2 Third method is called ====> 5.5 + -3.3 = 2.2 Second method is called ===> 5.5 + -3.3 = 2.2 Third method is called ====> 6.0 + 5.25 = 11.25 Second method is called ===> 6.0 + 5.25 = 11.25 Third method is called ====> 6.0 + 5.25 = 11.25
كما لاحظت هنا, في كل مرة قمنا فيها باستدعاء الدالة sum(), وجدنا أن المترجم قام باستدعاء الدالة sum() المناسبة لنوع الباراميترات الذي كنا نمرره لها.
تعريف دوال لها نفس الإسم و تختلف في عدد الباراميترات في جافا
لنفترض أننا قمنا بتعريف كلاس إسمه MyInfo, يحتوي على ثلاث دوال إسمهم displayInfo() و نوعهم public void.
الدالة الأولى تأخذ باراميتر واحد عبارة عن الإسم, ثم تقوم بطباعته.
الدالة الثانية تأخذ إثنين باراميتر عبارة عن الإسم و إسم العائلة, ثم تقوم بطباعتهما.
الدالة الثالثة تأخذ ثلاثة باراميترات عبارة عن الإسم و إسم العائلة و الوظيفة, ثم تقوم بطباعتهم.
بعد إنشاء هذا الكلاس, سنقوم بإنشاء الكلاس Main و إستدعاء الدوال الثلاثة فيه.
مثال
public class MyInfo {
public void displayInfo(String name) {
System.out.println("name: " +name);
System.out.println("---------------------");
}
public void displayInfo(String name, String lastName) {
System.out.println("name: " +name);
System.out.println("last name: " +lastName);
System.out.println("---------------------");
}
public void displayInfo(String name, String lastName, String job) {
System.out.println("name: " +name);
System.out.println("last name: " +lastName);
System.out.println("job: " +job);
System.out.println("---------------------");
}
}
public class Main {
public static void main(String[] args) {
MyInfo m = new MyInfo(); // لإستدعاء الدوال منه MyInfo هنا قمنا بإنشاء كائن من الكلاس
m.displayInfo("Mhamad"); // هنا سيتم إستدعاء الدالة التي تأخذ باراميتر واحد
m.displayInfo("Mhamad", "Harmush"); // هنا سيتم إستدعاء الدالة التي تأخذ 2 باراميتر
m.displayInfo("Mhamad", "Harmush", "programmer"); // هنا سيتم إستدعاء الدالة التي تأخذ 3 باراميترات
}
}
•سنحصل على النتيجة التالية عند التشغيل.
name: Mhamad --------------------- name: Mhamad last name: Harmush --------------------- name: Mhamad last name: Harmush job: programmer ---------------------
كما لاحظت هنا, في كل مرة قمنا فيها باستدعاء الدالة displayInfo(), وجدنا أن المترجم قام باستدعاء الدالة displayInfo() التي تحتوي على نفس عدد الباراميترات الذي كنا نمرره لها.
تعريف دوال تعتمد على دوال موجودة قبلها في نفس الكلاس في جافا
المثال التالي هو من أهم أمثلة الـ Overloading لأننا سنبني دوال تعتمد على دوال موجودة قبلها.
الآن لنفترض أننا قمنا بتعريف كلاس إسمه MyMath, يحتوي على ثلاث دوال إسمهم max() و نوعهم public double.
الدالة الأولى تأخذ 2 باراميتر عبارة عن أرقام, و هي تعطينا العدد الأكبر بينهما.
الدالة الثانية تأخذ 3 باراميترات عبارة عن أرقام, و هي تعطينا العدد الأكبر بينهم.
الدالة الثالثة تأخذ 4 باراميترات عبارة عن أرقام, و هي تعطينا العدد الأكبر بينهم.
بعد إنشاء هذا الكلاس, سنقوم بإنشاء الكلاس Main و إستدعاء الدوال الثلاثة فيه.
مثال
package overloading;
public class MyMath {
// هذه الدالة تعطيها رقمين فترجع لك العدد الأكبر بينهما
public double max(double a, double b) {
if(a>b)
return a;
else
return b;
}
// هذه الدالة تعطيها ثلاثة أرقام فترجع لك العدد الأكبر بينهم
// و هي تعتمد على الدالة السابقة لمقارنة أول عددين مع العدد الثالث
public double max(double a, double b, double c) {
if(max(a,b)>c)
return max(a,b);
else
return c;
}
// هذه الدالة تعطيها ثلاثة أرقام فترجع لك العدد الأكبر بينهم
// و هي تعتمد على الدالتين السابقتين لمقارنة أول ثلاث أعداد مع العدد الرابع
public double max(double a, double b, double c, double d) {
if(max(a,b,c)>d)
return max(a,b,c);
else
return d;
}
}
public class Main {
public static void main(String[] args) {
MyMath m = new MyMath(); // لإستدعاء الدوال منه MyMath هنا قمنا بإنشاء كائن من الكلاس
System.out.println("The max number is: " +m.max(5, 20)); // هنا سيتم إستدعاء الدالة التي تأخذ 2 باراميتر
System.out.println("The max number is: " +m.max(5, 20, 15)); // هنا سيتم إستدعاء الدالة التي تأخذ 3 باراميترات
System.out.println("The max number is: " +m.max(5, 20, 15, 30)); // هنا سيتم إستدعاء الدالة التي تأخذ 4 باراميترات
}
}
•سنحصل على النتيجة التالية عند التشغيل.
The max number is: 20.0 The max number is: 20.0 The max number is: 30.0
كما لاحظت هنا, في كل مرة قمنا فيها باستدعاء الدالة max(), وجدنا أن المترجم قام باستدعاء الدالة max() التي تحتوي على نفس عدد الباراميترات الذي كنا نمرره لها, و داخلياً ربطنا الدوال ببعضها.
تعريف دوال تعتمد على دوال موجودة قبلها في الـ Superclass في جافا
المثال التالي هو من أهم أمثلة الـ Overloading لأننا سنبني دالة في الـ Subclass تعتمد على دالة موجودة في الـ Superclass.
الآن لنفترض أننا قمنا بتعريف كلاس إسمه MyMath1, يحتوي دالة إسمها max(), الهدف من هذه الدالة مقارنة أي عددين نمررهما لها كـ Arguments ثم إرجاع العدد الأكبر بينهما.
بعد مدة قررنا إنشاء نسخة متطورة من هذا الكلاس إسمها MyMath2 تعتمد على الكلاس MyMath1, إذاً الكلاس الجديد يحتوي على نسخة متطورة من الدالة max() تقارن ثلاث أرقام.
بعد إنشاء هذه الكلاسات, سنقوم بإنشاء الكلاس Main و إستدعاء كلا الدالتين فيه من خلال اكائن من االكلاس MyMath2.
مثال
public class MyMath1 {
// هذه الدالة تعطيها رقمين فترجع لك العدد الأكبر بينهما
public double max(double a, double b) {
if(a>b)
return a;
else
return b;
}
}
public class MyMath2 extends MyMath1 {
// هذه الدالة تعطيها ثلاثة أرقام فترجع لك العدد الأكبر بينهم
// Superclass الموروثة من الـ max() و هي تعتمد على الدالة
public double max(double a, double b, double c) {
if(max(a,b)>c)
return max(a,b);
else
return c;
}
}
public class Main {
public static void main(String[] args) {
MyMath2 m = new MyMath2(); // لإستدعاء الدوال منه MyMath2 هنا قمنا بإنشاء كائن من الكلاس
System.out.println("The max number is: " +m.max(25, 14)); // هنا سيتم إستدعاء الدالة التي تأخذ 2 باراميتر
System.out.println("The max number is: " +m.max(25, 14, 35)); // هنا سيتم إستدعاء الدالة التي تأخذ 3 باراميترات
}
}
•سنحصل على النتيجة التالية عند التشغيل.
The max number is: 25.0 The max number is: 35.0
كما لاحظت هنا أن الـ MyMath2 يملك دلتين إسمهم max(), الأولى هي التي ورثها من الـ MyMath1, و الثانية التي قمنا بتعريفها فيه.
في كل مرة قمنا فيها باستدعاء الدالة max(), وجدنا أن المترجم قام باستدعاء الدالة max() التي تحتوي على نفس عدد الباراميترات الذي كنا نمرره لها, و داخلياً ربطنا الدوال ببعضها.
تعريف أكثر من كونستركتور في الكلاس في جافا
المثال التالي هو من أهم أمثلة الـ Overloading لأننا سنقوم بتجهيز أكثر من كونستركتور للكلاس حتى نسهل طريقة خلق كائنات منه.
الآن لنفترض أننا قمنا بتعريف كلاس إسمه Country (أي بلد), يحتوي على أربعة Constructors.
الكونستركتور الأول لا يأخذ أي باراميتر.
الكونستركتور الثاني يأخذ باراميتر واحد عبارة عن إسم البلد.
الكونستركتور الثالث يأخذ إثنين باراميتر عبارة عن إسم و مساحة البلد.
الكونستركتور الرابع تأخذ ثلاثة باراميترات عبارة عن إسم و مساحة و عدد سكان البلد.
بعد إنشاء هذا الكلاس, سنقوم بإنشاء الكلاس Main و إنشاء أربعة كائنات من الكلاس Country.
مثال
public class Country {
// سيملك هذه الخصائص, أي كل بلد سيملك هذه الخصائص Country كل كائن من الكلاس
public String name;
public double area;
public long population;
// الكونستركتور الأول لا يأخذ باراميترات
public Country() {
}
// الكونستركتور الثاني يتيح لك تحديد إسم البلد مباشرةً عند إنشاء الكائن
public Country(String n) {
name = n;
}
// الكونستركتور الثالث يتيح لك تحديد إسم البلد و مساحته مباشرةً عند إنشاء الكائن
public Country(String n, double a) {
name = n;
area = a;
}
// الكونستركتور الرابع يتيح لك تحديد إسم البلد و مساحته و عدد السكان مباشرةً عند إنشاء الكائن
public Country(String n, double a, long p) {
name = n;
area = a;
population = p;
}
// هذه الدالة تعرض جميع المعلومات التي تم إدخالها في الكائن
public void printInfo() {
System.out.println("name: " + name);
System.out.println("area: " + area);
System.out.println("population: " + population);
System.out.println("-------------------");
}
}
public class Main {
public static void main(String[] args) {
// هنا كل كائن يمثل بلد ,Country هنا قمنا بإنشاء 4 كائنات من الكلاس
Country c1 = new Country(); // c1 هنا سيتم إستدعاء الكونستركور الأول, لن يتم تحديد أي معلومة حول البلد الذي يمثله الكائن
Country c2 = new Country("KSA"); // c2 هنا سيتم إستدعاء الكونستركور الثاني, أي سيتم تحديد إسم البلد الذي يمثله الكائن
Country c3 = new Country("Turkey", 780580); // c3 هنا سيتم إستدعاء الكونستركور الثالث, أي سيتم تحديد إسم و مساحة البلد الذي يمثله الكائن
Country c4 = new Country("Lebanon", 10452, 4467000); // c4 هنا سيتم إستدعاء الكونستركور الربع, أي سيتم تحديد إسم و مساحة و عدد سكان البلد الذي يمثله الكائن
// لعرض خصائص جميع الكائنات printInfo() هنا قمنا باستدعاء الدالة
c1.printInfo();
c2.printInfo();
c3.printInfo();
c4.printInfo();
}
}
•سنحصل على النتيجة التالية عند التشغيل.
name: null area: 0.0 population: 0 ------------------- name: KSA area: 0.0 population: 0 ------------------- name: Turkey area: 780580.0 population: 0 ------------------- name: Lebanon area: 10452.0 population: 4467000 -------------------
كما لاحظت هنا أن الـ Country يملك أربعة Constructors إسمهم Country(), و كل Constructor منهم يتيح لك تحديد معلومات معينة مباشرةً عند إنشاء كائن من الكلاس Country.
في كل مرة قمنا فيها باستدعاء الكونستركتور Country(), وجدنا أن المترجم قام باستدعاء الكونستركتور Country() الذي يحتوي على نفس عدد الباراميترات الذي كنا نمرره له.
مفهوم الـ Type Casting
Type Casting تعني تحويل نوع الكائن أو المتغير إلى نوع شبيه له. هذا التحويل يمكن تحقيقه فقط بين Superclass و Subclass.
بالنسبة لتحويل أنواع الكائنات. يوجد أسلوبين للتحويل و هما: Upcasting و Downcasting.
Upcasting تعني تحويل نوع كائن من الـ Subclass إلى نوع كائن من الـ Superclass.
Downcasting تعني تحويل نوع كائن من الـ Superclass إلى نوع كائن الـ Subclass.
التحويل في كلا الحالتين لا يقصد منه تحويل نوع الكائن الأصلي, بل تخصيص الطريقة التي سيتعامل فيها المترجم مع الكائن.
إذا قمت بإنشاء كلاس إسمه A, ثم قمت بإنشاء كلاس آخر إسمه B يرث منه. و بعدها فعلت DownCast لكائن من A على أنه B, هنا الكائن سيبقى كائن من A و لكن المترجم سيتعامل معه ككائن من B, و سيخفي خصائص الكلاس A فيه. سترى ذلك لاحقاً من خلال الأمثلة.
طريقة تحويل أنواع الكائنات
في الصورة التالية سترى كيف نفعل عمليات الـ Upcasting و الـ Downcasting.
مصطلحات تقنية
تذكر دائماً أن الـ Upcasting هي التحويل من الأسفل إلى الأعلى, و الـ Downcasting هي التحويل من الأعلى إلى الأسفل كما في الصورة التالية:
Automatic Upcasting and Manual Upcasting
بالنسبة للـ Upcasting, يمكنك عدم تحديد نوع الـ Superclass المستخدم في التحويل لأن المترجم يفعل ذلك بشكل تلقائي.
مثال
•الطريقتين المذكورتين هنا مسموحتان في جافا و يعطيانك نفس النتيجة.
// Manual Upcasting <-- الطريقة الأولى A a = (A) new B(); // Automatic Upcasting <-- الطريقة الثانية A a = new B();
الطريقة الأولى تسمى Manual Upcasting لأننا قمنا بذكر النوع الذي سيتم تحويل الكائن إليه.
الطريقة الثانية تسمى Automatic Upcasting لأننا لم نقم بذكر النوع الذي سيتم تحويل الكائن إليه, و بالتالي سيقوم المترجم بذلك بشكل تلقائي.
في الأمثلة التالية سنستخدم أسلوب الـ Automatic Upcasting و ليس الـ Manual Upcasting.
نقطة مهمة حول الـ Downcasting
بالنسبة للـ Downcasting, يجب تحديد نوع الـ Subclass المستخدم في التحويل لأن المترجم لا يفعل ذلك بشكل تلقائي, و السبب في ذلك أن التحويل من الأعلى إلى الأسفل بشكل عام يعني أن الكائن الذي تم إنشائه من الـ Superclass سيخسر بعض بياناته عند تحويله إلى كائن من الـ Subclass.
إذاً لا يمكن أن تفعل DownCast لكائن إلا إذا كنت قد سبق و فعلت له UpCast.
المثال الأول
A a = new B(); B b = a; // Incompatible Type: A cannot be converted to B <-- [ سيظهر لك تحذير ]
•إذاً هنا كان يجب تحويل نوع الكائن a حتى تحل المشكلة.
A a = new B(); B b = (B) a; // هنا حللنا المشكلة و أصبح الكود سليم لا يحتوي على أي خطأ
المثال الثاني
B b = new A(); // Incompatible Type: A cannot be converted to B <-- [ سيظهر لك تحذير ]
•إذاً هنا كان يجب تحويل نوع الكائن a حتى تحل المشكلة.
B b = (B) new A(); // هنا حللنا المشكلة و أصبح الكود سليم لا يحتوي على أي خطأ
أمثلة شاملة
سنقوم بتعريف كلاس إسمه Person عبارة عن شخص, بعدها سنقوم بتعريف كلاس إسمه Male و كلاس إسمه Female, و الإثنان يرثان من الكلاس Person. أي يجب ربط الكلاسات كما في الصورة التالية:
الآن سنقوم ببناء جميع هذه الكلاسات, ثم سنقوم ببناء الكلاس Main لتجربة مفهوم الـ Type Casting.
إنتبه: سنستخدم الدالة getClass() لمعرفة الكلاس الأصلي للكائنات.
المثال الأول
public class Person {
}
public class Male extends Person {
}
public class Female extends Person {
}
public class Main {
public static void main(String[] args) {
Female hala = new Female(); // hala إسمه Female هنا قمنا بتعريف كائن عادي من
Male mhamad = new Male(); // mhamad إسمه Male هنا قمنا بتعريف كائن عادي من
// Upcasting هنا سنجري عملية تحويل من النوع
Person ziad = new Male(); // Male ثم حددنا أنه من النوع Person هنا قمنا بتعريف كائن من
// Downcasting هنا سنجري عملية تحويل من النوع
Female rola = new Female(); // rola إسمه Female هنا قمنا بتعريف كائن عادي من
Person p = rola; // Upcasting هذه العملية تسمى .rola ثم حددنا أنه يشير إلى الكائن Person هنا قمنا بتعريف كائن من
Female rana = (Female) p; // Downcasting هذه العملية تسمى .rola و الذي يشير في الأصل للكائن p للكائن DownCast هنا فعلنا
// هنا سنقوم بطباعة إسم الكلاس المشتق منه كل كائن قمنا بإنشائه
System.out.println("1) "+ hala.getClass()); // Female كائن من الكلاس hala هنا ستخبرنا أن
System.out.println("2) "+ mhamad.getClass()); // male كائن من الكلاس mhamad هنا ستخبرنا أن
System.out.println("3) "+ ziad.getClass()); // male كائن من الكلاس ziad هنا ستخبرنا أن
System.out.println("4) "+ rola.getClass()); // female كائن من الكلاس rola هنا ستخبرنا أن
System.out.println("5) "+ p.getClass()); // Female و الذي نوعه rola لأنه أصلاً يشير إلى الكائن Female كائن من الكلاس p هنا ستخبرنا أن
System.out.println("6) "+ rana.getClass()); // Female كائن من الكلاس rana هنا ستخبرنا أن
}
}
•سنحصل على النتيجة التالية عند التشغيل.
1) class typecasting.Female 2) class typecasting.Male 3) class typecasting.Male 4) class typecasting.Female 5) class typecasting.Female 6) class typecasting.Female
حسناً, لقد فهمت الآن طريقة التحويل لكنك حتماً مرتبك بشأن نتيجة التشغيل, و لعلك تتساءل إذا كانت الكائنات rola, و p و rana كلها تشير إلى نفس الكائن؟!".
سنعلمك حيلة تجعلك تتأكد إذا كان يوجد أكثر من إسم يشير إلى نفس الكائن.
كل ما عليك أن تفعله هو طباعة عناوين الخانات التي تم تخزين الكائنات فيها في الذاكرة.
سنعيد نفس المثال السابق مع عرض عناوين الخانات التي تم تخزين الكائنات فيها في الذاكرة, و نلفت الإنتباه أن هذه النتيجة تتغير من حيث أسماء العناوين التي ستظهر كلما قمت بتشغيل البرنامج (ذاكرة الحاسوب تعمل هكذا), لكن الفكرة هنا أن المتغيرات التي تشير إلى نفس الكائن ستشير إليه نفسه في الذاكرة (أي ستشير إلى نفس العنوان في الذاكرة) و هذا ما يؤكد لنا أنهم يشيرون إليه.
المثال الثاني
public class Main {
public static void main(String[] args) {
Female hala = new Female();
Male mhamad = new Male();
Person ziad = new Male();
Female rola = new Female();
Person p = rola;
Female rana = (Female) p;
// هنا سنقوم بطباعة عنوان كل كائن تم إنشائه في الذاكرة
System.out.println("1) "+ hala.toString()); // في الذاكرة hala هذا العنوان الذي يخزن الكائن
System.out.println("2) "+ mhamad.toString()); // في الذاكرة mhamad هذا العنوان الذي يخزن الكائن
System.out.println("3) "+ ziad.toString()); // في الذاكرة ziad هذا العنوان الذي يخزن الكائن
System.out.println("4) "+ rola.toString()); // في الذاكرة rola هذا العنوان الذي يخزن الكائن
System.out.println("5) "+ p.toString()); // في الذاكرة p هذا العنوان الذي يخزن الكائن
System.out.println("6) "+ rana.toString()); // في الذاكرة rana هذا العنوان الذي يخزن الكائن
}
}
•سنحصل على نتيجة تشبه النتيجة التالية عند التشغيل لأن الأماكن المحجوزة في الذاكرة لتخزين بيانات البرامج تتبدل كما قلنا سابقاً.
•قمنا بتعليم العناوين المشتركة باللون الأصفر.
1) typecasting.Female@60ec2ea8 2) typecasting.Male@31eb494e 3) typecasting.Male@4e19b97c 4) typecasting.Female@7ae0a3f2 5) typecasting.Female@7ae0a3f2 6) typecasting.Female@7ae0a3f2
•إذاً الكائنات الثلاثة rola, p و rana يشيرون فعلاً إلى نفس الكائن. و الكائن هنا هو كائن من الكلاس Female.
الفكرة الأخيرة التي عليك التأكد منها هي أنه عندما نقول أن الكائن نوعه كذا و لكنه بالتحديد من النوع كذا, فإن النوع الذي حددته له هو النوع الذي سيمثل الكائن.
مثال
Person ziad = new Male();
هنا سيعتبر المترجم أن الكائن ziad هو كائن من Male و ليس من Person.
و بالتالي سيحتوي الكائن ziad على نسخة من الأشياء الموجودة في Male و ليس الأشياء الموجودة في Person.
سنعيد نفس المثال السابق لكننا سنضيف دالة إسمها print() في الكلاس Person و سنفعل لها Override في الكلاسَين Male و Female.
المثال الثالث
public class Person {
public void print() {
System.out.println("I am a Person");
}
}
public class Male extends Person {
@Override
public void print() {
System.out.println("I am a Person and i'm a Male too.");
}
}
public class Female extends Person {
@Override
public void print() {
System.out.println("I am a Person and i'm a Female too.");
}
}
public class Main {
public static void main(String[] args) {
Female hala = new Female(); // Female سيملك نسخة من أشياء الكلاس hala الكائن
Male mhamad = new Male(); // Male سيملك نسخة من أشياء الكلاس hala الكائن
Person ziad = new Male(); // Male سيملك نسخة من أشياء الكلاس ziad الكائن
Female rola = new Female(); // Female سيملك نسخة من أشياء الكلاس rola الكائن
Person p = rola; // Female و بالتالي نسخة من أشياء الكلاس rola سيملك نسخة من أشياء الكائن p الكائن
Female rana = (Female) p; // Female و الذي يعتبر في الأصل نسخة من p سيملك نسخة من أشياء الكائن rana الكائن
hala.print(); // Female الموجودة في الكلاس print() هنا سيتم إستدعاء الدالة
mhamad.print(); // Male الموجودة في الكلاس print() هنا سيتم إستدعاء الدالة
ziad.print(); // Male الموجودة في الكلاس print() هنا سيتم إستدعاء الدالة
rola.print(); // Female الموجودة في الكلاس print() هنا سيتم إستدعاء الدالة
p.print(); // Female الموجودة في الكلاس print() هنا سيتم إستدعاء الدالة
rana.print(); // Female الموجودة في الكلاس print() هنا سيتم إستدعاء الدالة
}
}
•سنحصل على النتيجة التالية عند التشغيل.
I am a Person and i'm a Female too. I am a Person and i'm a Male too. I am a Person and i'm a Male too. I am a Person and i'm a Female too. I am a Person and i'm a Female too. I am a Person and i'm a Female too.
تحويل أنواع البيانات البدائية
بالنسبة لأنواع البيانات البدائية (int, long, float, double, char) يمكنك أن تفعل لهم Cast عند الحاجة بكل سهولة.
مثال
public class Main {
public static void main(String[] args) {
double a = 10.55;
// b و تخزينها في المتغير int إلى النوع double من النوع a هنا قمنا بتحويل نوع قيمة المتغير
int b = (int)a;
// أصلاً يقبل أعداد صحيحة double لأن النوع c مباشرةً في المتغير b هنا قمنا بتخزين قيمة المتغير
double c = b;
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("c = " + c);
}
}
•سنحصل على النتيجة التالية عند التشغيل.
a = 10.55 b = 10 c = 10.0
إنتبه جيداً أثناء التحويل لأنك كما شاهدت في المثال السابق أنك قد تخسر بعض البيانات عند التحويل.
أخطاء محتملة قد تحدث عند التحويل
إذا لم تقم بتحويل الكائنات بطريقة صحيحة, سيؤدي ذلك إلى ظهور أخطاء في الكود, قمنا بتقسيمها إلى ثلاثة إحتمالات:
خطأ من النوع RuntimeException و هذا الخطأ يظهر لك قبل تشغيل البرنامج.
خطأ من النوع ClassCastException و هذا الخطأ يظهر لك أثناء تشغيل البرنامج.
وضعنا هنا أمثلة حول أنواع الأخطاء التي قد تحدث عند تحويل أنواع الكائنات.
لنفترض أننا قمنا بإنشاء ثلاث كلاسات: A و B و C. ثم قلنا أن B و C يرثان من A.
بعد إنشاء هذه الكلاسات, سنقوم بإنشاء الكلاس Main و تجربة عمليات الـ Type Casting فيه.
ملاحظة: في الأمثلة التالية سنقوم بتعريف جميع الكلاسات في الملف Main.java.
المثال الأول
•في الكود التالي يوجد خطأ من النوع RuntimeException
هنا الخطأ سيظهر قبل تشغيل البرنامج لأننا حاولنا أن نفعل Downcasting بدون أن نحدد نوع الـ Subclass الذي نريد تحويل الكائن إليه.
class A {
}
class B extends A {
}
class C extends A {
}
public class Main {
public static void main(String[] args) {
B b = new A(); // Incompatible Type: A cannot be converted to B <-- [ سيظهر لك تحذير ]
}
}
•سيظهر الخطأ التالي قبل و بعد التشغيل.
Uncompilable source code - incompatible types: typecasting.A cannot be converted to typecasting.B
المثال الثاني
•في الكود التالي يوجد خطأ من النوع ClassCastException
هنا الخطأ سيظهر بعد تشغيل البرنامج لأننا حاولنا أن نفعل Downcasting لكائن لم يتم تحديده ككائن من B قبل محاولة تحويله إلى كائن من B.
بمعنى آخر, المشكلة هنا أننا حاولنا أن نفعل Downcasting قبل أن نفعل Upcasting.
class A {
}
class B extends A {
}
class C extends A {
}
public class Main {
public static void main(String[] args) {
B b = (B) new A(); // هنا لن يظهر الخطأ إلا عند تشغيل البرنامج
}
}
•سيظهر الخطأ التالي بعد التشغيل.
typecasting.A cannot be cast to typecasting.B
المثال الثالث
•في الكود التالي حللنا المشكلتين السابقتين.
هنا لن يظهر أي خطأ لأننا فعلنا Upcasting و Downcasting بالطريقة الصحيحة.
class A {
}
class B extends A {
}
class C extends A {
}
public class Main {
public static void main(String[] args) {
A a = new B(); // Upcasting هنا فعلنا
B b = (B) a; // Downcasting هنا فعلنا
}
}
•هنا لن يظهر أي خطأ عند التشغيل.
المثال الرابع
•في الكود التالي يوجد خطأ من النوع RuntimeException
•في الكود التالي سبب الخطأ أننا حاولنا تحويل نوع كائن إلى نوع آخر لا يوجد بينهما علاقة وراثة.
class A {
}
class B extends A {
}
class C extends A {
}
public class Main {
public static void main(String[] args) {
B b = new C(); // Incompatible Type: C cannot be converted to B <-- [ سيظهر لك تحذير ]
}
}
•سيظهر الخطأ التالي قبل و بعد التشغيل.
Uncompilable source code - incompatible types: typecasting.C cannot be converted to typecasting.B