GagorAcadmey

تعلم برمجة تطبيقات الاندرويد من الصفر حتي الاحتراف

آخر المواضيع

‏إظهار الرسائل ذات التسميات تعلم الجافا. إظهار كافة الرسائل
‏إظهار الرسائل ذات التسميات تعلم الجافا. إظهار كافة الرسائل

الثلاثاء، 4 سبتمبر 2018

سبتمبر 04, 2018

Casting in java

مفهوم الـ Type Casting

Type Casting تعني تحويل نوع الكائن أو المتغير إلى نوع شبيه له. هذا التحويل يمكن تحقيقه فقط بين Superclass و Subclass.
بالنسبة لتحويل أنواع الكائنات. يوجد أسلوبين للتحويل و هما: Upcasting و Downcasting.
  1. Upcasting تعني تحويل نوع كائن من الـ Subclass إلى نوع كائن من الـ Superclass.

  2. 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();

  1. الطريقة الأولى تسمى Manual Upcasting لأننا قمنا بذكر النوع الذي سيتم تحويل الكائن إليه.

  2. الطريقة الثانية تسمى 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();
B b = a;// Incompatible Type: A cannot be converted to B  <-- [ سيظهر لك تحذير ]
إذاً هنا كان يجب تحويل نوع الكائن a حتى تحل المشكلة.
A a = new B();
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

إنتبه جيداً أثناء التحويل لأنك كما شاهدت في المثال السابق أنك قد تخسر بعض البيانات عند التحويل.

أخطاء محتملة قد تحدث عند التحويل

إذا لم تقم بتحويل الكائنات بطريقة صحيحة, سيؤدي ذلك إلى ظهور أخطاء في الكود, قمنا بتقسيمها إلى ثلاثة إحتمالات:
  1. خطأ من النوع RuntimeException و هذا الخطأ يظهر لك قبل تشغيل البرنامج.

  2. خطأ من النوع ClassCastException و هذا الخطأ يظهر لك أثناء تشغيل البرنامج.


وضعنا هنا أمثلة حول أنواع الأخطاء التي قد تحدث عند تحويل أنواع الكائنات.

المثال الأول

في الكود التالي يوجد خطأ من النوع 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
سبتمبر 04, 2018

التحميل الزائد للدوال

مفهوم الـ Overloading

Overloading: تعني تعريف أكثر من دالة أو كونستركتور, لهم نفس الإسم, مختلفون في عدد أو نوع الباراميترات.
الفكرة من الـ Overloading, هي تجهيز عدة دوال لهم نفس الإسم, هذه الدوال تكون متشابهة من حيث الوظيفة, مختلفة قليلاً في الأداء.
فعلياً, تكون كل دالة تحتوي على ميزات إضافية عن الدالة التي أنشأت قبلها.

شروط الـ Overloading

  1. الـ Overloading يطبق فقط على الدوال و الـ Constructors.

  2. يجب أن يملكوا نفس الإسم.

  3. يجب أن يختلفوا في نوع أو عدد الباراميترات.

  4. نوع الإرجاع غير مهم, لا يستطيع المترجم التفريق بين الدوال إذا كانوا مختلفين في نوع الإرجاع.

أمثلة شاملة حول الـ Overloading


المثال الأول

تعريف دوال لها نفس الإسم و تختلف في نوع الباراميترات.
لنفترض أننا قمنا بتعريف كلاس إسمه 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("ahmed");// هنا سيتم إستدعاء الدالة التي تأخذ باراميتر واحد
m.displayInfo("Mohmmed", "gagoor");// هنا سيتم إستدعاء الدالة التي تأخذ 2 باراميتر
m.displayInfo("ahmed", "gagoor", "programmer");// هنا سيتم إستدعاء الدالة التي تأخذ 3 باراميترات
}
}
سنحصل على النتيجة التالية عند التشغيل.
name: ahmed
---------------------
name: Mohmmed
last name: gagoor
---------------------
name: ahmed
last name: gagoor
job: programmer
---------------------

كما لاحظت هنا, في كل مرة قمنا فيها باستدعاء الدالة displayInfo(), وجدنا أن المترجم قام باستدعاء الدالة displayInfo() التي تحتوي على نفس عدد الباراميترات الذي كنا نمرره لها.

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

تعريف دوال تعتمد على دوال موجودة قبلها في نفس الكلاس.
المثال التالي هو من أهم أمثلة الـ Overloading لأننا سنبني دوال تعتمد على دوال موجودة قبلها.
الآن لنفترض أننا قمنا بتعريف كلاس إسمه MyMath, يحتوي على ثلاث دوال إسمهم max() و نوعهم public double.
  1. الدالة الأولى تأخذ 2 باراميتر عبارة عن أرقام, و هي تعطينا العدد الأكبر بينهما.

  2. الدالة الثانية تأخذ 3 باراميترات عبارة عن أرقام, و هي تعطينا العدد الأكبر بينهم.

  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 printInfo() {
System.out.println("name: " + name);
System.out.println("area: " + area);
System.out.println("population: " + population);
}
}
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("sudan", 20000);// c3 هنا سيتم إستدعاء الكونستركور الثالث, أي سيتم تحديد إسم و مساحة البلد الذي يمثله الكائن
Country c4 = new Country("cairo", 34344, 656666);// 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: sudan
area: 20000.0
population: 0
-------------------
name: cairo
area: 34344.0
population: 656666
-------------------

كما لاحظت هنا أن الـ Country يملك أربعة Constructors إسمهم Country(), و كل Constructor منهم يتيح لك تحديد معلومات معينة مباشرةً عند إنشاء كائن من الكلاس Country.
في كل مرة قمنا فيها باستدعاء الكونستركتور Country(), وجدنا أن المترجم قام باستدعاء الكونستركتور Country() الذي يحتوي على نفس عدد الباراميترات الذي كنا نمرره له.
سبتمبر 04, 2018

الواجهات في جافا

مفهوم الإنترفيس

interface هي كلمة محجوزة في جافا, و تكتب إنترفيس في اللغة العربية.
في دروس سابقة رأينا أنه في جافا لا يمكن للكلاس العادي أن يرث من أكثر من كلاس في نفس الوقت لأنه يمكن للكلاس أن يفعل extends لكلاس واحد فقط.
من أجل ذلك عمل مطوروا لغة جافا على إبتكار نوع جديد يشبه الكلاس العادي و يسمح لنا بتطبيق مبدأ تعدد الوراثة الذي يعتبر شيء مهم جداً في لغات البرمجة. هذا النوع الجديد يسمى interface.
يستخدم الإنترفيس كوسيط لتطوير البرامج, و المقصود هنا أن المبرمج يستخدم الإنترفيس إذا أراد جعل برنامجه قابلاً للتطوير بكل مرونة سواءً لأجل نفسه أو من قبل مبرمجين آخرين, فهو يستخدم الإنترفيس لإجبار المبرمجين على اتباع أسلوب محدد يضمن توافق الكودات التي سيكتبها المطورون الآخرون مع كودات البرنامج الأساسية.
إذاً الإنترفيس يستخدم لتجهيز أسلوب يمكن السير عليه لربط الكلاسات بأسلوب سهل, منطقي و مفهوم. سترى قوة الإنترفيس الحقيقية في درس الـ Polymorphism لكننا سنركز في هذا الدرس على طريقة التعامل مع الإنترفيس في جافا.

التعامل مع الإنترفيس

التعامل مع الإنترفيس يختلف عن التعامل مع الكلاس العادي, و هو يشبه التعامل مع الـ Abstract Class.
فعلياً الإنترفيس يطبق مبدأ الـ Full Abstraction.

الأشياء التي يمكن تعريفها بداخل الإنترفيس

  1. دوال لا تملك body, أي Abstract Method.

  2. متغيرات مع إعطائهم قيمة بشكل مباشرةً عند تعريفهم. لأن أي متغير تقوم بتعرفه بداخل الإنترفيس يعتبر معرف كـ public final static بشكل تلقائي.

  3. Nested Classes, أي كلاس نوعه static بداخل كلاس نوعه static.

  4. Nested Interfaces, أي إنترفيس بداخل إنترفيس.


طريقة تعريف الإنترفيس

الإنترفيس هو في الأساس Full Abstract, لكن لا يجب وضع الكلمة abstract عند تعريفه. و لا يمكن تعريف إنترفيس كـ prviate أو protected لأنه دائماً يعتبر public حتى لو لم تضع كلمة public قبله. كما أنه لا يمكن تعريف الإنترفيس كـ final أو static لأنه تم تصميم الإنترفيس لجعل أي كلاس يرثه يفعل Override للدوال الموجودة فيه.
إذاً لتعريف إنترفيس, أكتب interface ثم ضع له أي إسم تريده.
interface MyInterface {

}

طريقة تعريف متغيرات في الإنترفيس

متغيرات الإنترفيس تعتبر public final static حتى لو لم تقم بتعريفها كذلك, الأمر الذي يجعلك مجبراً على إعطاءها قيمة مباشرةً عند تعريفها مع عدم إمكانية تغيير هذه القيمة.
interface MyInterface {
int X = 1;// حتى و لم نعرفه كذلك public final static يعتبر معرف كـ X المتغير
}


طريقة تعريف دوال في الإنترفيس

دوال الإنترفيس هي في الأساس Abstract Method, إذاً لا حاجة إلى وضع الكلمة abstract عند تعريف أي دالة بداخله. كما أن أي دالة يتم تعريفها بداخله تعتبر public حتى لو لم تعرفها كـ public. و لا يمكن تعريف الدوال فيه كـ prviate أو protected أو final أو static.
interface MyInterface {

void myMethod(); // حتى و لم نعرفها كذلك public abstract تعتبر معرفة كـ myMethod() الدالة
long myResult(); // حتى و لم نعرفها كذلك public abstract تعتبر معرفة كـ myResult() الدالة

}

شروط الربط بين الكلاس و الإنترفيس

  1. لا يمكن إنشاء كائن من إنترفيس.

  2. يستطيع الكلاس أن يرث من كلاس واحد, أي يستطيع أن يفعل extends لكلاس واحد.

  3. لا يستطيع الكلاس أن يرث من إنترفيس, أي لا يستطيع أن يفعل extends لإنترفيس.

  4. يستطيع الكلاس تنفيذ إنترفيس أو أكثر, أي يستطيع أن يفعل implements لإنترفيس أو أكثر.

  5. الكلاس الذي ينفذ إنترفيس, يجب أن يفعل Override لجميع الدوال التي ورثها من هذا الإنترفيس .

  6. يستطيع الإنترفيس أن يرث من إنترفيس أو أكثر. أي يستطيع الإنترفيس أن يفعل extends لإنترفيس أو أكثر.



تنفيذ الإنترفيس

عند تنفيذ أي إنترفيس, يجب أن تفعل Override لجميع الدوال الموجودة فيه, و يجب تعريفهم كـ public حتى يستطيع أي كائن من هذا الكلاس أن يستخدمهم.
A.java
interface A { // لجميع الدوال الموجودة فيه Override أي كلاس ينفذ هذا الإنترفيس يجب أن يفعل
void print(); }
B.java
class B implements A { // A ينفذ الإنترفيس B هنا قلنا أن الكلاس
  @Override // public لا تنسى إضافة كلمة .print() للدالة Override مجبور أن يفعل B الكلاس
public void print() {
System.out.println("B should Override this method");
    }
}

شروط أساسية عليك اتباعها عند إنشاء إنترفيس

  1. لا تستخدم أي Access Modifer عند تعريف إنترفيس.

  2. لا تستخدم أي Access Modifer عند تعريف دالة بداخل إنترفيس.

  3. بداخل الإنترفيس جميع الدوال يجب أن لا تملك body. و يمكن جعل الدالة ترمي إستثناء.

  4. لا يمكن للإنترفيس أن يملك كونستركتور.

الأسباب التي تدفعك إلى استخدام الإنترفيس

  1. إذا كنت تنوي بناء كلاس يطبق مفهوم الـ Full Abstraction.

  2. لحل مشكلة تعدد الوراثة.

  3. لتطبيق مبدأ تعدد الأشكال Polymorphism.

الفرق بين الكلمتين extends و implements

  1. extends تعني وراثة, نستخدمها لجعل كلاس يرث من كلاس, أو لجعل إنترفيس يرث من إنترفيس أو أكثر.

  2. implements تعني تنفيذ, نستخدمها لجعل كلاس ينفذ إنترفيس أو أكثر.


الصورة التالية توضح لك متى يمكن إستخدام الكلمتين extends و implements.

متى تكون مجبراً على أن تفعل Override و متى لا

في حالة الـ extends:
  1. إذا كان عندك كلاس يرث من كلاس, يكون للـ Subclass الحرية في أن يفعل Override للدوال التي ورثها.

  2. إذا كان عندك كلاس يرث من كلاس نوعه abstract, يكون الـ Subclass مجبراً على أن يفعل Override للدوال المعرفة كـ abstract.

  3. إذا كان عندك إنترفيس يرث من إنترفيس, لا يستطيع الـ SubInterface أن يفعل Override للدوال التي ورثها.

في حالة الـ implements:
  1. إذا كان عندك كلاس ينفذ إنترفيس, يكون الـ Subclass مجبراً على أن يفعل Override للدوال التي ورثها.

  2. إذا كان عندك كلاس نوعه abstract ينفذ إنترفيس, يكون الـ Subclass له الحرية في أن يفعل Override للدوال التي ورثها.

أشكال تعدد الوراثة في جافا

إذا قام كلاس بتنفيذ أكثر من إنترفيس, أو إذا قام إنترفيس بوراثة أكثر من إنترفيس, تسمى هذه العمليات وراثة متعددة (Multiple Inheritance).
الصورة التالية توضح لك شكل الوراثة المتعددة.

الوراثة من أكثر من كلاس في جافا

ملاحظة

إذا كان الكلاس ينفذ أكثر من إنترفيس, يجب وضع فاصلة بينهم.
في حال كان الكلاس يرث من كلاس آخر و ينفذ إنترفيس أو أكثر, إفعل extends للكلاس في البداية ثم إفعل implements لأي إنترفيس تريد.

المثال الأول

هنا قمنا بتعريف إثنين إنترفيس A و B, و قمنا بتعريف كلاس إسمه C و قلنا أن C ينفذ A و B.
interface A { }

interface B { }

class C implements A, B { } // لكل دالة ورثها منهما Override بمعنى أنه يجب أن يفعل B و الإنترفيس A ينفذ الإنترفيس C هنا الكلاس


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

هنا قمنا بتعريف ثلاثة إنترفيس A ,B و C, و قلنا أن C يرث من A و B.
interface A { }

interface B { }

interface C extends A, B { } // B و الإنترفيس A يرث من الإنترفيس C هنا الإنترفيس


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

هنا قمنا بتعريف إنترفيس A, كلاس B و كلاس C يرث من B و ينفذ A.
interface A { }

class B { }

class C extends B implements A { } // A و ينفذ الإنترفيس B يرث من الكلاس C هنا الكلاس

أمثلة توضيحية علي ما سبق

المثال الأول

في هذا المثال قمنا بتعريف إثنين إنترفيس A و B:

  1. الإنترفيس A يملك دالة إسمها printA().

  2. الإنترفيس B يملك دالة إسمها printB().

ثم قمنا بتعريف كلاس إسمه C و قلنا أن C ينفذ A و B.


A.java
public interface A {
 
    void printA();
 
}

B.java
public interface B {
 
    void printB();
 
}

C.java
public class C implements A, B {// لجميع الدوال التي ورثها Override إذاً يجب أن يفعل B و الإنترفيس A ينفذ الإنترفيس C هنا الكلاس
 
    @Override// printA() للدالة Override مجبور أن يفعل C الكلاس
    void printA() {
        System.out.println("C should Override the method printA()");
    }
 
    @Override// printB() للدالة Override مجبور أن يفعل C الكلاس
    void printB() {
        System.out.println("C should Override the method printB()");
    }
 
}

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

في هذا المثال قمنا بتعريف إنترفيس A يملك دالة إسمها printA().
و إنترفيس B يرث من الإنترفيس A و يملك دالة إسمها printB().

ثم قمنا بتعريف كلاس إسمه C يطبق الإنترفيس B, و بالتالي عليه أن يفعل Override لجميع الدوال التي ورثها.

A.java
public interface A {
 
    void printA();
 
}

B.java
public interface B extends A {
 
    void printB();
 
}

C.java
public class C implements B {// printB() و printA() للدالتين Override إذاً يجب أن يفعل .B و الذي بدوره يرث من الإنترفيس B يطبق من الإنترفيس C هنا الكلاس
 
    @Override// printA() للدالة Override مجبور أن يفعل C الكلاس
    void printA() {
        System.out.println("C should Override the method printA()");
    }
 
    @Override// printB() للدالة Override مجبور أن يفعل C الكلاس
    void printB() {
        System.out.println("C should Override the method printB()");
    }
 
}

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

في هذا المثال قمنا بتعريف إنترفيس A, كلاس B و كلاس C يرث من B و ينفذ A:

  1. الإنترفيس A يملك دالة إسمها printA().

  2. الإنترفيس B يملك دالة إسمها printB().


A.java
public interface A {
 
    void printA();
 
}

B.java
public public class B {
 
    void printB() {
        System.out.println("C not need to Override the method printB()");
    }
 
}

C.java
public class C extends B implements A {// A و ينفذ الإنترفيس B يرث من الكلاس C هنا الكلاس
 
    @Override// printA() للدالة Override مجبور أن يفعل C الكلاس
    void printA() {
        System.out.println("C should Override the method printA()");
    }
 
}

Nested Interfaces

يمكنك تعريف إنترفيس بداخل إنترفيس بداخل إنترفيس إلخ.. ويمكنك تنفيذ الإنترفيس الذي تريده منهم بالتحديد متى شئت.

المثال الأول

هنا قمنا بتعريف إنترفيس A, و بداخله إنترفيس B, و بداخله إنترفيس C
ثم قمنا بتعريف كلاس D ينفذ A, و كلاس E ينفذ B, و كلاس F ينفذ C
interface A { // A نكتب A لنصل للإنترفيس
interface B { // A.B نكتب A الموجود بداخل الإنترفيس B لنصل للإنترفيس
interface C { // A.B.C نكتب A الموجود بداخل الإنترفيس B الموجود بداخل الإنترفيس C لنصل للإنترفيس
     }
   }
}

class D implements A { } // A ينفذ الإنترفيس D هنا الكلاس

class E implements A.B { } // B ينفذ الإنترفيس E هنا الكلاس

class F implements A.B.C { } // C ينفذ الإنترفيس F هنا الكلاس

يمكنك أن تفعل import للإنترفيس و عندها يمكنك أن تكتب إسمه فقط للوصول إليه.

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

هنا سنفعل import للإنترفيس B و import للإنترفيس C بدل أن نصل إليهم من الإنترفيس A.
package interfaces;

import interfaces.A.B; // لذلك أصبح يمكننا الوصول إليه مباشرةً B هنا قمنا بتحديد المكان الموجود فيه الإنترفيس
import interfaces.A.B.C; // لذلك أصبح يمكننا الوصول إليه مباشرةً C هنا قمنا بتحديد المكان الموجود فيه الإنترفيس

interface A { // A نكتب A لنصل للإنترفيس
interface B { // B نكتب A الموجود بداخل الإنترفيس B لنصل للإنترفيس
interface C { // C نكتب A الموجود بداخل الإنترفيس B الموجود بداخل الإنترفيس C لنصل للإنترفيس
     }
}
}

class D implements A { } // A ينفذ الإنترفيس D هنا الكلاس

class E implements B { } // B ينفذ الإنترفيس E هنا الكلاس

class F implements C { } // C ينفذ الإنترفيس F هنا الكلاس

إنتبه:
هنا الإنترفيس B لا يعتبر أنه يرث من الإنترفيس A. كما أن الإنترفيس C لا يعتبر أنه يرث من الإنترفيس B.

Tagged Interfaces

Tagged Interface يقال لها أيضاً Marker Interface, فكرتها تقسيم الـ Interfaces المتشابهين في مجموعات.
لتقسيم الـ Interfaces إلى مجموعات ننشئ إنترفيس فارغ, ثم نجعل كل إنترفيس يشبهه من حيث الفكرة يرث منه.
الآن لنفترض أننا نريد إنشاء مجموعة خاصة للألوان. أول شيء نفعله هو إنشاء إنترفيس فارغ خاص للألوان إسمه Colors.
بعدها نجعل كل إنترفيس له علاقة بالألوان يرث منه, و هكذا سيعتبر المترجم أن كل إنترفيس يرث من Colors هو من النوع Colors.

interface Colors { }
interface RGB implements Colors { } // Colors أي ينتمي إلى المجموعة Colors يرث من الإنترفيس RGB الإنترفيس
interface PMS implements Colors { } // Colors أي ينتمي إلى المجموعة Colors يرث من الإنترفيس PMS الإنترفيس
interface HEX implements Colors { } // Colors أي ينتمي إلى المجموعة Colors يرث من الإنترفيس HEX الإنترفيس
interface HSL implements Colors { } // Colors أي ينتمي إلى المجموعة Colors يرث من الإنترفيس HSL الإنترفيس
في دروس سابقة قلنا أنه في جافا لا يمكن للكلاس أن يرث من أكثر من كلاس في نفس الوقت, إذاً لا يستطيع الكلاس أن يفعل extends لأكثر من كلاس.

مثال

لنفترض أننا قمنا بتعريف ثلاث كلاسات a, B و C. ثم قلنا أن C يرث من A و B.
class A { }

class B { }

class C extends A, B { } // هنا سيظهر لك تحذير و لن يعرف أصلاً المشكلة لأن هذا الأسلوب غير معرف في جافا
إذاً وراثة أكثر من كلاس ممنوعة في جافا.

من هنا قام مطوروا جافا ببناء شيء شبيه للكلاس إسمه interface الهدف الأساسي منه كان دعم فكرة تعدد الوراثة.
الآن إذا عدنا للمثال السابق يمكننا إنجاح عملية تعدد الوراثة باستخدام interface بدل class.

هنا قمنا بتعريف إثنين إنترفيس A و B, و قمنا بتعريف كلاس إسمه C و قلنا أن C يرث من A و B.
interface A { }

interface B { }

class C implements A, B { } // B و الإنترفيس A يرث من الإنترفيس C هنا الكلاس
هنا سيكون الكلاس C مجبر أن يفعل Override لكل دالة موجودة في A و B.

إذاً في جافا يمكنك تنفيذ أكثر من إنترفيس في نفس الوقت.
كلمة implements شبيهة جداً للكلمة extends لكننا وضعناها بدلاً منها لسبب ستفهمه لاحقاً.
بما أن الكلاس يستطيع أن ينفذ أكثر من interface في نفس الوقت, يمكننا القول أن جافا تدعم تعدد الوراثة من خلال الـ interfaces.

سبتمبر 04, 2018

مبدأ التغليف في جافا

مفهوم التغليف

التغليف: يعني Encapsulation في اللغة الإنجليزية. و هو عبارة عن أسلوب يمكن اتباعه لإخفاء البيانات الأساسية في الكلاس, أي لإخفاء الخصائص الموجودة فيه ( Global Variables ) , و جعل الكلاسات الأخرى قادرة على التعامل مع هذه الخصائص فقط من خلال دوال يقوم بإنشائها المبرمج الأساسي للكلاس.

الأسلوب المتبع في عملية التغليف

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

مفهوم دوال الـ setter و الـ getter

عند التعامل مع أي متغير ( أو خاصية ) فعندك خيارين و هما إما إعطاءه قيمة جديدة و إما الحصول على القيمة الموجودة فيه. و بما أنه يجب بناء دوال للتعامل مع كل خاصية من الخصائص الموجودة في الكلاس, ينصح بإعتماد أسماء متعارف عليها كالتالي:
  1. إبدأ إسم كل دالة الهدف منها إعطاء قيمة للخاصية بالكلمة set ثم إسم الخاصية.

  2. إبدأ إسم كل دالة الهدف منها الحصول على قيمة الخاصية بالكلمة get ثم إسم الخاصية.



أمثلة شاملة

الآن سنقوم بإنشاء كلاس إسمه Employee و فكرته تخزين معلومات الموظفين مثل الإسم name, الراتب salary, العمر age.
بعدها سنقوم بتجربة الكلاس Employee في الكلاس Main.

المثال الأول

هنا لم نطبق مبدأ التغليف.
Employee.java
package employee;
public class Employee {
// يملك 3 خصائص Employee الكلاس
String name; // لأنه عبارة عن نص String الإسم نوعه
int age; // لأنه عبارة عن رقم int العمر نوعه
double salary; // لأنه عبارة عن رقم كبير يمكن أن يحتوي على فاصلة double الراتب نوعه
}

Main.java
package employee;
public class Main {
public static void main(String[] args) {
// Employee من الكلاس e هنا قمنا بإنشاء الكائن
Employee e = new Employee();
// e هنا قمنا بوضع قيم لخصائص الكائن
e.name = "Mhamad";
e.age = 21;
e.salary = 1500000;
// e هنا قمنا بعرض قيم خصائص الكائن
System.out.println("Name: " +e.name);
System.out.println("Age: " +e.age);
System.out.println("Salary: " +e.salary);
    }
}

سنحصل على النتيجة التالية عند التشغيل.
Name: Mhamad
Age: 21
Salary: 1500000.0


الآن سنقوم بتعريف الخصائص كـ private و سنقوم بتعريف دوال نوعها public للتعامل مع هذه الخصائص.

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

هنا قمنا بتطبيق مبدأ التغليف.
Employee.java
package employee;
public class Employee {
// private الآن قمنا بتعريف الخصائص كـ
private String name;
private int age;
private double salary;
public String getName() { // name هذه الدالة ترجع قيمة المخزنة الخاصية
return name;
}
public int getAge() { // age هذه الدالة ترجع قيمة المخزنة الخاصية
return age;
}
public double getSalary() { // salary هذه الدالة ترجع قيمة المخزنة الخاصية
return salary;
}
public void setName(String n) { // name هذه الدالة نعطيها إسم فتقوم بوضعه للخاصية
name = n;
}
public void setAge(int a) { // age هذه الدالة نعطيها رقم فتقوم بوضعه للخاصية
age = a;
}
public void setSalary(double s) { // salary هذه الدالة نعطيها رقم فتقوم بوضعه للخاصية
salary = s;
     }
}

Main.java
package employee;
public class Main {
public static void main(String[] args) {
// Employee من الكلاس e هنا قمنا بإنشاء الكائن
Employee e = new Employee();
// Setter من خلال دوال الـ e هنا قمنا بوضع قيم لخصائص الكائن
e.setName("Mhamad");
e.setAge(21);
e.setSalary(1500000);
// Getter من خلال دوال الـ e هنا قمنا بعرض قيم خصائص الكائن
System.out.println("Name: " +e.getName());
System.out.println("Age: " +e.getAge());
System.out.println("Salary: " +e.getSalary());
     }
}

سنحصل على النتيجة التالية عند التشغيل.
Name: Mhamad
Age: 21
Salary: 1500000.0


من فوائد التغليف أيضاً أنه يتيح لك وضع شروط لتخزين البيانات, كما أنه يتيح لك الحصول على البيانات بالطريقة التي تريدها.
الآن سنقوم بإضافة بعض التعديلات على دوال الـ Setter و الـ Getter.

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

هنا قمنا بتطبيق مبدأ التغليف مع وضع بعض القيود عند إدخال البيانات, و إضافة بعض التعديلات عند جلب البيانات.
قمنا بتعليم الشروط و التعديلات الجديدة باللون الرمادي.
Employee.java
package employee;
public class Employee {
private String name;
private int age;
private double salary;
public String getName() { // مع إظهار جملة صغيرة قبلها name هذه الدالة ترجع قيمة الخاصية
return "Name: " +name;
}
public int getAge() {
return age;
}
public double getSalary() {
return salary;
}
public void setName(String n) { // بشرط أن يكون الإسم أكبر من 3 أحرف name هذه الدالة نعطيها رقم فتقوم بوضعه للخاصية
if (n.length() < 3) {
System.out.println("Name is too short, name can't be less then 3 characters!");
}
else {
name = n;
}
}
public void setAge(int a) { // age هذه الدالة نعطيها رقم فتقوم بوضعه للخاصية
age = a;
}
public void setSalary(double s) { // salary هذه الدالة نعطيها رقم فتقوم بوضعه للخاصية
salary = s;
    }
}

Main.java
package employee;
public class Main {
public static void main(String[] args) {
Employee e = new Employee();
e.setName("dj"); // لن يقبله لأنه أصغر من حرفين
e.setAge(21);
e.setSalary(1500000);
System.out.println(e.getName());
System.out.println("Age: " +e.getAge());
System.out.println("Salary: " +e.getSalary());
    }
}

سنحصل على النتيجة التالية عند التشغيل.
Name is too short, name can't be less then 3 characters!
Name: null
Age: 21
Salary: 1500000.0
لاحظ أنه لم يقبل الإسم الذي أدخلناه له لأنه أصغر من ثلاثة أحرف, لذلك طبع الرسالة التي قمنا بتجهيزها في حال تم إدخال قيمة أصغر من ثلاثة أحرف, كما أنه لم يضع الإسم الذي قمنا بإدخاله في الخاصية name لأنه كما نلاحظ أن الدالة getName() قامت بإرجاع القيمة null


فوائد التغليف

  1. يمكنه جعل الأشياء الموجودة في الكلاس قابلة للقراءة أو للكتابة من قبل الكلاسات الخارجية.

  2. يسمح للكلاس بوضع شروط أثناء تخزين البيانات.

  3. التغليف يساعد أيضاً في جعل البرنامج قابل للتطوير من مبرمجين آخرين بدون حاجة هؤلاء المبرمجين إلى معرفة تفاصيل الكود الأساسي في البرنامج.

سبتمبر 04, 2018

Abstraction in java


مفهوم الـ Abstraction

تجريد: تعني Abstraction في اللغة الإنجليزية, و هو أسلوب مهم جداً يستخدم لتسهيل كتابة الأوامر على المبرمجين, فهو يجعلك قادراً على تنفيذ ما تريد دون الحاجة إلى معرفة كافة التفاصيل التي تم فيها تنفيذ ذلك. إذاً الـ Abstraction يجعلك تتعامل مع الأشياء بسطحية بدل أن تغوص في معرفة تفاصيل الكودات المعقدة.
فمثلاً إذا كنت تنوي بناء برنامج يتيح لمستخدميه إرسال إقتراحات حول التطبيق من خلال البريد الإلكتروني, في هذه الحالة لن يكون عليك القلق بتاتاً حول طريقة تعامل أوامر جافا مع البروتوكولات التي تعمل عندما يتم إرسال رسائل إلكترونية من خلال هذا التطبيق, لأنك لست مسؤولاً عنها, فعلياً هذه وظيفة شركة Sun التي تقوم بتطوير لغة جافا. و هم يخبرونك أنه لإرسال بريد إلكتروني إستخدم هذه الأوامر البسيطة فقط.
إذاً الـ Abstraction هو أسلوب يستخدم لإخفاء تفاصيل تنفيذ البرنامج. لتطبيق مفهوم الـ Abstraction نستخدم الكلمة abstract ضمن شروط محددة.

مصطلحات تقنية

إذا أردت تعريف الشيء كـ abstract, أكتب فقط الكلمة abstract قبله.
  1. الكلاس المعرف كـ abstract يسمى Abstract Class.

  2. الكلاس العادي الغير معرف كـ abstract يسمى Concrete Class.

  3. الدالة المعرفة كـ abstract تسمى Abstract Method أو Abstract Function.

Abstract Class

إذا وضعت الكلمة abstract قبل إسم الكلاس, ستتغير قليلاً طريقة التعامل معه لأنه لم يعد كلاس عادي. و عندها سيراه المترجم كـ Abstract Class.

نقاط مهمة حول الـ Abstract Class

  1. الكلاس العادي لا يمكنه أن يحتوي على دوال نوعها abstract.

  2. الـ Abstract Class يمكنه أن يحتوي على دوال عادية, و يمكنه أن يحتوي على دوال نوعها abstract.

  3. إذا قمت بتعريف الكلاس كـ abstract, فهذا يعني أن هذا الكلاس لا يمكن إنشاء كائنات منه.

  4. بما أنه لا يمكن إنشاء كائنات من Abstract Class, فهذا يعني أنه للإستفادة من هذا الكلاس, يجب وراثته.

  5. الكلاس الذي يرث من كلاس نوعه abstract, يجب أن يفعل Override لجميع الدوال المعرفة كـ abstract.

طريقة تعريف كلاس نوعه abstract

نكتب فقط الكلمة abstract قبل الكلمة class.

مثال

abstract class Example {}

Abstract Method

إذا كنت تريد بناء دالة و جعل الكلاس الذي يرثها هو المسؤول عن كتابتة محتواها, قم بتعريفها كـ abstract.

نقاط مهمة حول الـ Abstract Method

  1. إذا وضعت الكلمة abstract قبل إسم الدالة, فهذا يعني أنها دالة من النوع abstract.

  2. الدالة التي نوعها abstract هي دالة لها إسم و نوع محدد, لكنها لا تحتوي على body (جسم), أي لا تملك أقواس بداية و نهاية { }

  3. الدالة العادية تحتوي على أقواس البداية و النهاية { }

  4. الـ Abstract Method يجب وضع فاصلة منقوطة ; في آخرها بدل أقواس البداية و النهاية.

  5. الكلاس الذي يرث من كلاس نوعه abstract, يجب أن يفعل Override لكل دوالة نوعها abstract, أي يجب أن يكتب الـ body لهذه الدوال.

طريقة تعريف دوال نوعها abstract

نكتب فقط الكلمة abstract بعد Modifier الدالة.

أمثلة

public abstract void print();
public abstract int getId();
public abstract void setId(int id);
إنتبه: عندما تفعل Override لدالة نوعها abstract, يجب أن لا تعرفها كـ abstract من جديد.

لنفترض أننا قمنا بتعريف دالة إسمها displayMessage() كـ abstract
public abstract void displayMessage(); لا يجب أن نكتب abstract عندما تفعل لها Override.
    @Override
publicabstract void displayMessage() {//abstract methods cannot have a body
  }
لتصحيح هذا الخطأ, نمسح فقط الكلمة abstract.
    @Override
public void displayMessage() {// نلاحظ أن التنبيه إختفى
  }
أمثلة بسيطة و مترابطة تعلمك أساسيات الـ Abstraction
الآن سنقوم بتعريف كلاس إسمه A, نوعه abstract, يملك متغير إسمه x, و دالة إسمها print(). بعدها سنقوم ببناء الكلاس Test لتجربة الكود.

المثال الأول

A.java
package abstraction;
public abstract class A {   // إذاً لا يمكن إنشاء كائنات منه ,abstract نوعه A الكلاس
int x;
public void print() {
System.out.println("This is just an example.");
   }
}
Test.java
package abstraction;
public class Test {
public static void main(String[] args) {
A a = new A();   // Incompatible Type: abstraction.A is abstract; cannot be instantiated
   }
}
نتيجة التنفيذ :
Exception in thread "main" java.lang.RuntimeException:Uncompilable source span - abstraction.A is abstract; cannot be instantiated
سبب الخطأ هنا أننا حاولنا إنشاء كائن من كلاس نوعه abstract.
الآن سنقوم بإنشاء كلاس جديد إسمه B, يرث من الكلاس A. بداخل الكلاس Test سننشئ كائن من الكلاس B لنحصل على الأشياء الموجودة في الكلاس A.

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

A.java
package abstraction;
public abstract class A {
int x;
public void print() {
System.out.println("This is just an example.");
    }
}
B.java
package abstraction;
public class B extends A {   // إذاً سيرث كل شيء موجود فيه .A يرث من B هنا قلنا أن الكلاس
}
Test.java
package abstraction;
public class Test {
public static void main(String[] args) {
B b = new B();// B هنا قمنا بإنشاء كائن من الكلاس
b.print();// A من الكلاس B التي ورثها الكلاس print() هنا قمنا باستدعاء الدالة
b.x = 10;// A من الكلاس B الذي ورثه الكلاس x هنا قمنا بتغيير قيمة المتغير
System.out.println("b.x contain: " + b.x);    // x هنا قمنا بعرض قيمة المتغير
   }
}
نتيجة التنفيذ :
This is just an example. b.x contain: 10
إذاً بما أننا لا نستطيع إنشاء كائن مباشرةً من الكلاس A, قمنا بإنشاء الكلاس B بهدف وراثة الأشياء الموجودة في الكلاس A, و بعدها أنشأنا كائن من الكلاس B و تأكدنا أنه يحتوي على جميع الأشياء الموجودة في الكلاس A.
الآن سنقوم بإضافة دالتين نوعهما abstract في الكلاس A, ثم سنفعل لهما Override في الكلاس B. بداخل الكلاس Test سننشئ كائن من الكلاس B و سنقوم باستدعاء الدوال الجديدة التي قمنا بإضفاتها.

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

A.java
package abstraction;
public abstract class A {
int x;
public void print() {
System.out.println("This is just an example.");
 }
public abstract void setX(int x);// كل كلاس يرثها Override إذاً يجب أن يفعل لها
public abstract int getX();// كل كلاس يرثها Override إذاً يجب أن يفعل لها
  }
B.java
package abstraction;
public class B extends A {// abstract لأي دالة سيرثها من النوع Override يجب أن يفعل A يرث من الكلاس B بما أن الكلاس
// setX() للدالة Override هنا فعلنا
    @Override
public void setX(int x) {
super.x = x;
   }
// setX() للدالة Override هنا فعلنا
    @Override
public int getX() {
return super.x;
    }
}
Test.java
package abstraction;
public class Test {
public static void main(String[] args) {
B b = new B();// B هنا قمنا بإنشاء كائن من الكلاس
b.print();// A من الكلاس B التي ورثها الكلاس print() هنا قمنا باستدعاء الدالة
b.setX(55);// setX() عن طريق الدالة A من الكلاس B الذي ورثه الكلاس x هنا قمنا بتغيير قيمة المتغير
System.out.println("b.x contain: " + b.getX());    // getX() عن طريق الدالة  x هنا قمنا بعرض قيمة المتغير
   }
}
نتيجة التنفيذ :
This is just an example. b.x contain: 55

مثال بسيط في جافا حول Abstract Class يرث من Abstract Class

الآن سنقوم بتعريف كلاس إسمه A نوعه abstract, يملك متغير إسمه x, و دالة إسمها print1() نوعها abstract. بعدها سنقوم بتعريف كلاس إسمه B نوعه abstract يرث من الكلاس A, يملك دالة إسمها print2() نوعها abstract. بعدها سنقوم بتعريف كلاس إسمه C, يرث من الكلاس B. في الأخير سنقوم ببناء الكلاس Test لتجربة الكود.
A.java
package abstraction;
public abstract class A {
int x;
public abstract void print1();
}
B.java<
package abstraction;
public abstract class B extends A {
public abstract void print2();
// print1() أيضاً ورث الدالة B لا تنسى أن الكلاس
}
C.java
package abstraction;
public class C extends B {
// B التي ورثها من الكلاس abstract لجميع الدوال التي نوعها Override يجب أن يفعل C الكلاس
   @Override
public void print1() {
System.out.println("Class C shoold override the method print1()");
}
   @Override
public void print2() {
System.out.println("Class C shoold override the method print2()");
}
}
Test.java
package abstraction;
public class Test {
public static void main(String[] args) {
C c = new C();   // C هنا قمنا بإنشاء كائن من الكلاس
c.print1();// Override و فعل لها C التي ورثها الكلاس print1() هنا قمنا باستدعاء الدالة
c.print2();// Override و فعل لها C التي ورثها الكلاس print2() هنا قمنا باستدعاء الدالة
    }
}
نتيجة التنفيذ :
Class C shoold override the method print1()
Class C shoold override the method print2()

مثال مهم يعلمك متى تحتاج إلى إنشاء Abstract Class

الآن سنقوم ببناء كلاس إسمه Person نوعه abstract, يملك أربعة خصائص نوعهم private إسمهم name, gender, brithday و isMarried, و يملك دوال Setter and Getter لهذه الخصائص, و يملك أيضاً دالة إسمها displayInfo() نوعها abstract.
بعدها سنقوم بتعريف كلاس إسمه Student يرث من الكلاس Person و يملك متغير إضافي إسمه specialization.
بعدها سنقوم بتعريف كلاس إسمه Employee يرث من الكلاس Person و يملك متغير إضافي إسمه workPlace.
الفكرة هنا أن أي كلاس سيتم إنشاءه لتمثيل إنسان يجب أن يرث من الكلاس Person الذي يملك الخصائص المشتركة لكل البشر.
في الأخير سنقوم ببناء الكلاس Test لتجربة الكود.

Person.java
package abstraction;
public abstract class Person {
// هنا قمنا بتعريف الخصائص المشتركة لدى جميع البشر
String name;
String gender;
String birthday;
boolean isMarried;
// هنا قمنا بتعريف الكونستركتور
public Person(String n, String g, String b, boolean i) {
name = n;
gender = g;
birthday = b;
isMarried = i;
}
// Getter هنا قمنا بتعريف دوال الـ
public String getName() {
return name;
}
public String getGender() {
return gender;
}
public String getBirthday() {
return birthday;
}
public boolean getIsMarried() {
return isMarried;
}
 // Setter هنا قمنا بتعريف دوال الـ
public void setName(String n) {
name = n;
}
public void setGender(String g) {
gender = g;
}
public void setBirthday(String b) {
birthday = b;
 }
public void setIsMarried(boolean i) {
isMarried = i;
}
// abstract و التي نوعها displayInfo() هنا قمنا بتعريف الدالة
public abstract void displayInfo();
}
Student.java
package abstraction;
public class Student extends Person {
String specialization;   // هنا قمنا بتعريف خاصية التخصص و التي يملكها فقط التلاميذ
// هنا قمنا بتعريف الكونستركتور
public Student(String n, String g, String b, boolean i, String s) {
super(n, g, b, i);    // Person هنا سيتم إرسال أول أربع قيم إلى كونستركتور الكلاس
specialization = s;
}
// بشكل ملائم للتلامذة displayInfo() للدالة Override هنا فعلنا
    @Override
public void displayInfo() {
System.out.println("Name: " + getName());
System.out.println("Gender: " + getGender());
System.out.println("Birthday: " + getBirthday());
System.out.println("Specialization: " + specialization);
System.out.println("---------------------------------");
 }
}
Employee.java
package abstraction;
public class Employee extends Person {
String workPlace;   // هنا قمنا بتعريف خاصية مكان العمل و التي يملكها فقط الموظفون و العمال
// هنا قمنا بتعريف الكونستركتور
public Employee(String n, String g, String b, boolean i, String w) {
super(n, g, b, i);    // Person هنا سيتم إرسال أول أربع قيم إلى كونستركتور الكلاس
workPlace = w;
}
// بشكل ملائم للموظفين أو العمال displayInfo() للدالة Override هنا فعلنا
   @Override
public void displayInfo() {
System.out.println("Name: " + getName());
System.out.println("Gender: " + getGender());
System.out.println("Birthday: " + getBirthday());
if(getIsMarried() == true)
System.out.println("is Married: yes");
else
System.out.println("is Married: no");
System.out.println("Work place: " + workPlace);
System.out.println("---------------------------------");
}
}

Test.java
package abstraction;
public class Test {
public static void main(String[] args) {
// و هو عبارة عن إنسان له خصائص طالب Student هنا قمنا بإنشاء كائن من الكلاس
Student s = new Student("Mhamad", "Male", "1994", false, "Computer Science");
s.displayInfo();
// و هو عبارة عن إنسان له خصائص موظف أو عامل Employee هنا قمنا بإنشاء كائن من الكلاس
Employee e = new Employee("Rana", "Female", "1986", true, "Al-Iman school");
e.displayInfo();
   }
}
سنحصل على النتيجة التالية عند التشغيل.
Name: Mhamad
            Gender: Male
            Birthday: 1994
            Specialization: Computer Science
            ---------------------------------
            Name: Rana
            Gender: Female
            Birthday: 1986
            is Married: yes
            Work place: Al-Iman school
            ---------------------------------

صفحتنا علي الفيسبوك

التسميات

المتواجدين حاليا