القائمة الرئيسية

الصفحات

مفهوم ال overriding والOverloading في جافا

مفهوم الـ Overriding

في الدرس السابق, شاهدت كيف أن الـ Subclass يرث المتغيرات و الدوال الموجودة في الـ Superclass. و تعلمت أيضاً أنه يمكن للـ Subclass إعادة تعريف أي دالة ورثها من الـ Superclass شرط أن لا تكون معرفة كـ final, و كتابة الكلمة @Override قبل تعريفها من جديد حتى تتفادى حدوث مشاكل عند ترجمة الكود.

Override: تعني تعريف الدالة التي ورثها الـ Subclass من الـ Superclass من جديد, هذه الدالة الجديدة تكون مشابهة للدالة الموروثة من حيث الشكل فقط, أي لها نفس الإسم و النوع و عدد الباراميترات, لكن محتواها مختلف.

الهدف الحقيقي من الـ Overriding هو إتاحة الفرصة للـ Subclass ليعرف الدوال حسب حاجته.

في دروس متقدمة سنرث من كلاسات جاهزة في جافا, و نفعل Override للدوال الموجودة فيها لكي تناسب التطبيقات التي سنقوم ببنائها.


مثال

الآن لنفترض أننا قمنا بتعريف كلاس إسمه Country, يحتوي على دالة إسمها language().
بعدها قمنا بتعريف ثلاث كلاسات, و كلها ترث من Country, إذاً كلها ستحتوي على الدالة language().
هنا الفكرة أن أي كلاس يرث من Country قد يضطر إلى تعريف الدالة language() من جديد حتى تناسبه.

بعد إنشاء هذه الكلاسات, سنقوم بإنشاء الكلاس Main لتجربتهم.

Country.java
	  public class Country {     // هنا, هذا الكلاس يعتبر الكلاس الأساسي لأي دولة في العالم, إذاً يجب أن يرثه أي كلاس يمثل دولة

	  public void language() {
	  System.out.println("English");   // هنا قمنا بوضع اللغة الإنجليزية كلغة إفتراضية لجميع البلدان
	  }

	  }
	

Australia.java
	  public class Australia extends Country {

	  // من جديد لأن اللغة الإنجليزية هي لغة أستراليا language() هنا لا داعي لتعريف الدالة

	  }
	

Lebanon.java
	  public class Lebanon extends Country {

	  // من جديد لأن اللغة الإنجليزية ليست لغة لبنان language() هنا يجب تعريف الدالة
	  @Override
	  public void language() {
	  System.out.println("Arabic");
	  }

	  }
	

Spain.java
	  public class Spain extends Country {

	  // من جديد لأن اللغة الإنجليزية ليست لغة إسبانيا language() هنا يجب تعريف الدالة
	  @Override
	  public void language() {
	  System.out.println("Spanish");
	  }

	  }
	

Main.java
	  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 و إستدعاء الدوال الثلاثة فيه.


مثال

MyMethods.java
                    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));
	  }

	  }
	

Main.java
                    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 و إستدعاء الدوال الثلاثة فيه.


مثال

MyInfo.java
                    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("---------------------");
	  }

	  }
	

Main.java
                    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 و إستدعاء الدوال الثلاثة فيه.


مثال

MyMath.java
                    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;
	  }

	  }
	

Main.java
                    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.


مثال

MyMath1.java
                    public class MyMath1 {

	  // هذه الدالة تعطيها رقمين فترجع لك العدد الأكبر بينهما
	  public double max(double a, double b) {
	  if(a>b)
	  return a;
	  else
	  return b;
	  }

	  }
	

MyMath2.java
                    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;
	  }

	  }
	

Main.java
                    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.


مثال

Country.java
                    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("-------------------");
	  }

	  }
	

Main.java
                    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() لمعرفة الكلاس الأصلي للكائنات.

المثال الأول

Person.java
public class Person {

	  }
	

Male.java
public class Male extends Person {

	  }
	

Female.java
public class Female extends Person {

	  }
	

Main.java
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 كلها تشير إلى نفس الكائن؟!".

سنعلمك حيلة تجعلك تتأكد إذا كان يوجد أكثر من إسم يشير إلى نفس الكائن.
كل ما عليك أن تفعله هو طباعة عناوين الخانات التي تم تخزين الكائنات فيها في الذاكرة.

سنعيد نفس المثال السابق مع عرض عناوين الخانات التي تم تخزين الكائنات فيها في الذاكرة, و نلفت الإنتباه أن هذه النتيجة تتغير من حيث أسماء العناوين التي ستظهر كلما قمت بتشغيل البرنامج (ذاكرة الحاسوب تعمل هكذا), لكن الفكرة هنا أن المتغيرات التي تشير إلى نفس الكائن ستشير إليه نفسه في الذاكرة (أي ستشير إلى نفس العنوان في الذاكرة) و هذا ما يؤكد لنا أنهم يشيرون إليه.

المثال الثاني

Main.java
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.

المثال الثالث

Person.java
public class Person {

	  public void print() {
	  System.out.println("I am a Person");
	  }

	  }
	

Male.java
public class Male extends Person {

	  @Override
	  public void print() {
	  System.out.println("I am a Person and i'm a Male too.");
	  }

	  }
	

Female.java
public class Female extends Person {

	  @Override
	  public void print() {
	  System.out.println("I am a Person and i'm a Female too.");
	  }

	  }
	

Main.java
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 عند الحاجة بكل سهولة.

مثال

Main.java
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 الذي نريد تحويل الكائن إليه.

Main.java
                    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 <-- [ سيظهر لك تحذير ]

	  }

	  }
	

سيظهر الخطأ التالي قبل و بعد التشغيل.

Exception in thread "main" java.lang.RuntimeException:
Uncompilable source code - incompatible types: typecasting.A cannot be converted to typecasting.B


المثال الثاني

في الكود التالي يوجد خطأ من النوع ClassCastException
هنا الخطأ سيظهر بعد تشغيل البرنامج لأننا حاولنا أن نفعل Downcasting لكائن لم يتم تحديده ككائن من B قبل محاولة تحويله إلى كائن من B.
بمعنى آخر, المشكلة هنا أننا حاولنا أن نفعل Downcasting قبل أن نفعل Upcasting.

Main.java
                    class A {

	  }

	  class B extends A {

	  }

	  class C extends A {

	  }

	  public class Main {

	  public static void main(String[] args) {

	  B b = (B) new A();     // هنا لن يظهر الخطأ إلا عند تشغيل البرنامج

	  }

	  }
	

سيظهر الخطأ التالي بعد التشغيل.

Exception in thread "main" java.lang.ClassCastException:
typecasting.A cannot be cast to typecasting.B


المثال الثالث

في الكود التالي حللنا المشكلتين السابقتين.
هنا لن يظهر أي خطأ لأننا فعلنا Upcasting و Downcasting بالطريقة الصحيحة.

Main.java
                    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

في الكود التالي سبب الخطأ أننا حاولنا تحويل نوع كائن إلى نوع آخر لا يوجد بينهما علاقة وراثة.

Main.java
                    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 <-- [ سيظهر لك تحذير ]

	  }

	  }
	

سيظهر الخطأ التالي قبل و بعد التشغيل.

Exception in thread "main" java.lang.RuntimeException:
Uncompilable source code - incompatible types: typecasting.C cannot be converted to typecasting.B