Modularity الوحداتية
يُقسم البرنامج وحدات مستقلة، فيُعاد استخدام كل وحدة (reusability) في موضع آخر بلا إعادة كتابة منطقها.
COMPUTER SCIENCE · JAVA
ملخص مراجعة عربي لمقرر البرمجة الكائنية بجافا: أربعة فصول و169 شريحة ومصطلحات بلغتين، مرتّبة لقراءة طويلة دون إجهاد العين.
285 دقيقة من العمل المركّز، موزّعة على أربع جلسات.
اقرأ البطاقات والجداول، ثم أعد كتابة مثال الدائرة من الذاكرة.
ثبّت super وسلسلة البناء وجدول حدود الوصول قبل الانتقال.
قارن abstract class بـinterface في جدول واحد.
أجب دون الرجوع للملخص، ثم اقرأ تفسير كل سؤال أخطأت فيه.
أنجزت 0 من 6
يُحفظ التقدّم في متصفحك، ويُحدَّث عند تعليم أي فصل بـ«تمت المراجعة».
Chapter 1 · 43 slides
Introduction to Object-Oriented Programming
من فكرة أن البرنامج كائنات تتفاعل إلى أول صنف يُترجَم ويُشغَّل: المصطلحات، ثم النمذجة والمتطلبات، ثم الأعمدة الأربعة، وينتهي الفصل بصنف Circle وبوانيه ومحددات وصوله.
الفكرة. البرمجة الكائنية نموذج يصف البرنامج مجموعة كائنات تتفاعل، لا دوالًا منفصلة عن بياناتها. الكائن بنية تحمل حقولًا (fields أو attributes) ودوالًا (methods) تعمل على تلك الحقول، فيبقى السلوك ملاصقًا للبيانات التي يخدمها. كائنات الصنف الواحد تشترك في شكل البيانات والدوال، ويختلف كل كائن بقيمه، ولا تعمل منعزلة: يطلب أحدها من الآخر فعلًا فيُسمّى الطلب message.
Object-Oriented Programming: نموذج برمجة (programming paradigm) يقوم على الكائنات (objects) بوصفها وحدة التنظيم، بدل الدوال المنفصلة عن بياناتها.
Message: طلب تنفيذ فعل يوجّهه كائن إلى كائن آخر ليقوم بعملية ما.
جوهر الشريحة الافتتاحية أن العالم الحقيقي يُوصف بدقة كمجموعة كائنات تتفاعل؛ ومن هنا جاء النموذج، لا من ترتيب الملفات أو الشاشات.
ماذا يغطي الفصل.
UML (القسم 1.8).خطة الشريحة 3 (الترتيب الأصلي). أربعة أقسام: Introduction on OOP، ثم Basic Principles of Object Orientation (Abstraction، Encapsulation، Polymorphism، Inheritance)، ثم Objects and Classes، ثم Constructors. الأقسام 1.8 و1.9 هنا تغطي القسمين الأخيرين، والسابقة تغطي الأولين.
يُقسم البرنامج وحدات مستقلة، فيُعاد استخدام كل وحدة (reusability) في موضع آخر بلا إعادة كتابة منطقها.
تنظيم يطابق تصوّر المسألة، فيرتفع وضوح الكود (code readability) ويسهل العمل المشترك (collaboration) والمراجعة.
التصميم يبدأ من الأشياء. في متجر تُذكر أولًا Store وProduct وCustomer وPayment method، ثم تُوزَّع عليها البيانات والمسؤوليات؛ فالأصناف تسبق الجداول والشاشات في التفكير.
الصنف قالب (template وblueprint) لا يحمل قيم كائن بعينه، والكائن نسخة (instance) تحمل الحالة الحالية، وإنشاء النسخة يُسمّى instantiation.
شخص أو مكان أو شيء، له identity فريدة تميزه عن غيره، وstate، وbehavior.
فئة كائنات متشابهة، تصف شكل بياناتها ودوالها. يعرّف نوع الحقل، ولا يحمل قيمة حقل كائن بعينه.
متغير للكائن أو الصنف يصف خاصية من خصائصه. الشريحة تجمع في العنوان ثلاث تسميات: Data - Properties - Attributes، فالمرادفات الدقيقة field وproperty (وstate عند الحديث عن القيم)، وdata الكلمة العامة في العنوان نفسه.
فعل ينفذه الكائن؛ مرادفاته operation وfunction وbehavior. تقابل الدالة الحرة في النموذج الوظيفي.
القيم الحالية للحقول في لحظة معينة؛ تُتميّز الدائرة بـ radius والمستطيل بـ width وheight.
الأفعال المعرّفة بالدوال، واستدعاؤها طلب تنفيذ؛ مثل getArea() وgetPerimeter() وsetRadius().
مثال السيارة. داخل صنف car خصائص fuel وmaxspeed، ودوال refuel() وgetFuel() وsetSpeed() وgetSpeed() وdrive()؛ ويُضاف إليها move() وcalculateVehicleAge().
| العضو | النوع | الدور |
|---|---|---|
Name | String | اسم السيارة، يُقرأ بـ getName() ويُكتب بـ setName(String) |
MaxSpeed | Integer | أقصى سرعة، تُضبط بـ setSpeed() |
Price | Float | السعر، قيمة تخص الكائن لا الصنف |
Model | Integer | الطراز (وللشريحة حقل منفصل لسنة الصنع Build year). |
ثلاث نسخ من القالب نفسه، تختلف بالقيم لا بالشكل:
| السيارة | السعر | اللون | السنة |
|---|---|---|---|
AAA | 10K | Orange | 2015 |
BBB | 15K | Blue | 2018 |
CCC | 45K | Green | 2015 |
لماذا يهم. هذا التفريق أساس كل ما بعده: القالب يُكتب مرة في الصنف، والقيم تتعدد في الكائنات، وعليه يقوم التغليف ومثال Circle في القسم 1.8.
Price؟ في الكائن لا في الصنف. والصنف يعرّف نوع الحقل فقط، وكل كائن يحمل قيمته المستقلة.radius لكائن بعينه؟الفكرة. النموذج الكائني يجمّع البيانات والسلوك داخل كائن هو instance من صنف، فلا تُفصل الحقول عن دوالها. أما البرمجة الوظيفية فتبني البرنامج من دوال: دخل ثم خرج، كما في رسم الشريحة.
x → f → f(x)
Pure function: دالة بلا آثار جانبية (side-effects)، خرجها تابع لدخلها وحده.
First-class citizen: الدالة عندما يمكن تمريرها وسيطًا أو إرجاعها نتيجة، فتُعامل كأي قيمة أخرى.
| البرمجة الكائنية | البرمجة الوظيفية |
|---|---|
| وحدة التنظيم: الكائن | وحدة التنظيم: الدالة |
| البيانات والسلوك داخل الكائن نفسه | الدالة تأخذ دخلًا وتعيد خرجًا |
| الحالة محفوظة في الحقول | التركيز على دوال نقية بلا حالة خارجية |
لماذا يهم. الفرق بنيوي لا تفضيلي: وحدة التنظيم في النموذج الكائني هي الكائن (بيانات + سلوك)، وفي الوظيفي هي الدالة؛ والمطلوب في الاختبار تمييز النموذج لا الحكم على أحدهما.
الفكرة. نستخدم النموذج الكائني لصنع حل هو model يحقّق حاجة الزبون والعمل. والنموذج تبسيط للواقع، لأن تعقيد النظام كله لا يُستوعب دفعة واحدة، وهو وصف مكتمل من منظور واحد لا من كل المناظير.
Model: تبسيط للواقع، ووصف مكتمل للنظام من منظور معيّن.
ننمذج كي نُبصر البنية (visualize) ونحدّدها (specify) ونبنيها (construct) ونوثّقها (document)، سواء في المعمارية أو السلوك.
| نوع المتطلب | ماذا يجيب | أمثلة |
|---|---|---|
Functional | ماذا يفعل النظام | الميزات والوظائف ومتطلبات المستخدم (user requirements) |
Nonfunctional | كيف يكون النظام | quality attributes وخصائص المنتج وتوقعات المستخدم |
خمسة مبادئ للنمذجة.
ورسم الشريحة يحذّر من فجوة التواصل: كل حلقة تُدخل تفسيرًا جديدًا، فقد يصل المنتج إلى ما لم يرده المستخدم.
لماذا يهم. المتطلبات مدخل النموذج؛ ومتطلب فُهم خطأً يُنتج صنفًا في غير موضعه. لذلك تُصنَّف المتطلبات قبل التصميم.
الفكرة. نموذج Enhanced Entity–Relationship (أو Extended Entity–Relationship) يعرّف علاقات العمل لقاعدة علائقية، بينما OO data modeling يصمّم الكود. في رسم EER تبقى البيانات في جهة، والدوال Function 1 وFunction 2 وFunction 3 منفصلة عنها؛ وفي النموذج الكائني يحمل كل كائن بياناته ودواله معًا.
| في النموذج الكائني | في نموذج EER |
|---|---|
Class: كيان ذو دور واضح، له state وbehavior وidentity | Entity type: لبنة تصف بنية البيانات |
Object: نسخة معيّنة من صنف | Entity instance: ظهور واحد لكيان |
Association: دور الكائن داخل ارتباط | Relationship: ارتباط بين كيانات |
| وراثة الخصائص ووراثة السلوك | وراثة الخصائص فقط، بلا تمثيل للسلوك |
مثال الشريحة: PERSON بحقول NAME وPID وAGE، ثم STUDENT ومعه GPA، وEMPLOYEE ومعه SALARY.
والمعادل الكائني يظهر في مخطط UML للدائرة، حيث - تعني private و+ تعني public:
Circle ------------------------------- - radius: double = 1.0 - color: String = "red" ------------------------------- + Circle() + Circle(r: double) + Circle(r: double, c: String) + getRadius(): double + getColor(): String + getArea(): double
والقالب واحد والقيم تختلف:
c1: نصف القطر 2.0 واللون "blue".c2: نصف القطر 2.0 واللون "red".c3: نصف القطر 1.0 واللون "red".color ولا getPerimeter)؛ والكود في القسم 1.8 مبني على مخطط الشريحة 33، وفيه radius فقط مع getArea وgetPerimeter وsetRadius وgetRadius.OO يمثّل السلوك ويورّثه، وEER يورّث الخصائص فقط. والنمذجة الكائنية تُنجز غالبًا بـ UML.لماذا يهم. اختيار النموذج يحدّد ما يمكن تمثيله: EER لقاعدة علائقية بلا سلوك، وOO لكود فيه حالة وسلوك؛ ومخطط الدائرة هنا أول تطبيق عملي للنموذج الكائني.
الفكرة. أربعة مبادئ تجعل اللغة كائنية، وهي أعمدة OOP: التغليف (encapsulation) والتجريد (data abstraction) وتعدد الأشكال (polymorphism) والوراثة (inheritance). وهذا القسم للأولين.
| العمود | تعريف الشريحة بسطر | مثال الفصل |
|---|---|---|
Encapsulation | لا يكشف إلا المعلومة المختارة | حساب مصرفي: balance لا يُمس إلا عبر deposit وwithdraw |
Abstraction | يخفي التفاصيل المعقّدة لتقليل التعقيد | ريموت التلفاز: أزرار بلا دارات |
Inheritance | الكيانات ترث خصائص كيانات أخرى | امتداد JDK 1.8 إلى 1.9 |
Polymorphism | الكيانات لها أكثر من شكل | makeNoise() عند Dog وCat |
التغليف. إحاطة المحتوى وحمايته كالكبسولة: تُجمَع الخصائص والسلوك في وحدة واحدة هي الصنف، ثم يُقيَّد الوصول إلى الداخل، وهو ما يُسمّى information hiding أو data hiding. لا يكشف الكائن إلا ما يلزم بقية التطبيق، وتقنيته حقول private ودوال وصول public؛ فالحقل private لا يُرى من خارج الصنف. والغلاف حاجز يمنع فساد البيانات، وأكبر فائدته تعديل التنفيذ دون كسر كود من يستخدم الصنف.
وتصوّر الشريحة ذلك بالتلفاز: العميل يرى الواجهة وحدها ولا يرى التنفيذ ولا الحقول الخاصة، ولا يحتاج معرفة كيف يعمل الجهاز.
| ما يراه العميل (الواجهة) | ما يخفيه الصنف (التنفيذ) |
|---|---|
changeChannel(int) | Aerial وTube وCircuit Board |
changeVolume(int) وmuteVolume(boolean) | IR Sensor وControls |
powerOn() وpowerOff() | الحقول: channelNumber وonOff وvolumeLevel |
public class BankAccount {
private double balance;
public void deposit(double amount) {
if (amount > 0) balance += amount;
}
public void withdraw(double amount) {
if (amount > 0 && amount <= balance) balance -= amount;
}
public double getBalance() { return balance; }
}
الحقل مغلق، وكل تعديل يمرّ بدالة تتحقق من القيمة أولًا؛ فلا يمكن إيداع مبلغ سالب أو سحب أكثر من الرصيد.
التجريد. تمثيل الجوهر اللازم للوظيفة وإخفاء الزائد، فيقل التعقيد وتزيد إعادة الاستخدام، ويسمح بتسمية ما لا يُنشأ مباشرة لكنه أساس لكائنات تشترك في خصائص. وهو عمومًا تجاهل واعٍ لبعض الجوانب لفهم غيرها، أي تبسيط. وفي التصميم تُعمَّم أشياء مثل Viktor وJohn وRoger في فئة Person، والريموت يوضّح المعنى: أزرار التشغيل والصوت والقناة تكفي بلا معرفة الدارات.
والتحليل والتصميم تجريد: نموذج قاعدة البيانات يتجاهل الواجهة والسلوك، ونموذج المعمارية يتجاهل داخل الوحدات، والكود يجرّد وظائف التطبيق بتراكيب محدودة.
balance إلا عبر deposit أو withdraw. والتجريد يختار ما هو جوهري ليُظهره، والتغليف يغلّف الحالة ويفرض المرور عبر الواجهة ويحمي التنفيذ.abstraction وencapsulation؟الوراثة. آلية تنتقل بها خصائص صنف الأب (attributes وmethods) إلى صنف ابن (subclass)، فيُعاد استخدام المشترك. والابن يتخصص بزيادة ميزات أو تعديلها، وعدة أصناف تُعمَّم في أب واحد يجمع المشترك؛ والتخصيص والتعميم يبنيان شجرة أصناف.
أب ثم ابن واحد يتخصص فيه.
أب واحد وعدة أبناء يشتركون في خصائصه.
سلسلة أجيال: أب ثم ابن ثم حفيد.
مثال التمديد. JDK 1.8 يمتد إلى JDK 1.9 بثلاث خطوات، والأب لا يُعاد بناؤه من الصفر:
تعدد الأشكال. للكيان أكثر من شكل، ومن هنا poly. تُصمَّم الكائنات لتشترك في سلوك، فتُطبَّق الدالة نفسها على كائنات من أصناف مختلفة؛ والوراثة تمكّن التعدد، والتعدد يزيد الكود العام (generic code).
في الشريحة صنف Animal فيه picture وfood وhunger وboundaries وlocation، ودوال makeNoise() وeat() وsleep() وroam()؛ والأبناء Lion وHippo وTiger وDog وCat وWolf.
class Animal {
void makeNoise() { }
}
class Dog extends Animal {
void makeNoise() { /* barking */ }
}
class Cat extends Animal {
void makeNoise() { /* Meow */ }
}
الاسم واحد في الأصناف الثلاثة، والتنفيذ يختلف بحسب الصنف الذي يستقبل الاستدعاء.
// مقتطف: يُكتب داخل main
Animal a = new Dog();
a.makeNoise(); // barking
المرجع من نوع Animal والكائن من نوع Dog، فيُنفَّذ سلوك الكلب.
Animal؛ يُختار التنفيذ وقت التشغيل بحسب نوع الكائن الفعلي (method overriding)، لا بحسب نوع المرجع.لماذا يهم. الوراثة تنقل السلوك المشترك مرة واحدة، والتعدد يجعل makeNoise() شكلًا مختلفًا عند Dog وCat دون تغيير اسم الدالة.
الفكرة. كائنات النوع الواحد تُعرَّف بصنف مشترك هو قالب حقولها ودوالها. والصنف يوفّر بواني (constructors) تُستدعى بـ new لإنشاء الكائن وتهيئة حقوله، ويمكن إنشاء نسخ كثيرة مستقلة.
ترميز مخطط الصنف.
dataFieldName: dataFieldTypeClassName(parameterName: parameterType)methodName(parameterName: parameterType): returnTypename: ClassName ثم قيم الحقول.Circle ------------------------------------------ - radius: double ------------------------------------------ + Circle() + Circle(newRadius: double) + getArea(): double + getPerimeter(): double + setRadius(newRadius: double): void
ومنه ثلاث نسخ: circle1 بنصف قطر 1، وcircle2 بنصف قطر 25، وcircle3 بنصف قطر 125.
وهذا الصنف تعريف لا برنامج، لأنه بلا main:
public class Circle {
private double radius;
public Circle() {
radius = 1;
}
public Circle(double newRadius) {
radius = newRadius;
}
}
بانيان بالاسم نفسه ووسطاء مختلفين: هذا هو التحميل الزائد (overloading).
public double getArea() {
return radius * radius * Math.PI;
}
public double getPerimeter() {
return 2 * radius * Math.PI;
}
public void setRadius(double newRadius) {
radius = newRadius;
}
public double getRadius() {
return radius;
}
المساحة radius * radius * Math.PI لا 2 * radius * Math.PI؛ الثانية للمحيط.
package-private؛ والكود العملي يحتاج public ليبقى الباني متاحًا خارج الحزمة.public class TestCircle {
public static void main(String[] args) {
Circle circle1 = new Circle();
System.out.println("The area of the circle of radius "
+ circle1.getRadius() + " is " + circle1.getArea());
Circle circle2 = new Circle(25);
System.out.println("The area of the circle of radius "
+ circle2.getRadius() + " is " + circle2.getArea());
Circle circle3 = new Circle(125);
System.out.println("The area of the circle of radius "
+ circle3.getRadius() + " is " + circle3.getArea());
circle2.setRadius(100);
System.out.println("The area of the circle of radius "
+ circle2.getRadius() + " is " + circle2.getArea());
}
}
الصنف الذي يحوي main هو main class، وآلة Java تستدعيه لتشغيل البرنامج.
The area of the circle of radius 1.0 is 3.141592653589793
The area of the circle of radius 25.0 is 1963.4954084936207
The area of the circle of radius 125.0 is 49087.385212340516
The area of the circle of radius 100.0 is 31415.926535897932
تغيير circle2 وحده غيّر السطر الأخير، ولم يمسّ circle1 ولا circle3.
الترجمة. عند ترجمة TestCircle.java يبحث المترجم في مسار الأصناف (classpath) — والمجلد الحالي هو الافتراضي — عن Circle.class. فإن غاب بحث في sourcepath عن Circle.java وترجمه إلى Circle.class، ثم أتمّ ترجمة TestCircle.java.
Circle وحده؟ لأنه بلا main؛ التشغيل للصنف الرئيسي الذي ينشئ الكائنات ويستدعي دوالها.setRadius(100)؟125؛ لأن السطر الرابع ناتج عن تغيير حالة circle2 بعد إنشائه.الباني. نوع خاص من الدوال لتهيئة الكائن عند new، وله ثلاث سمات تُحفظ:
void.new عند إنشاء الكائن، ودوره تهيئة حقوله.ويُحمَّل تحميلاً زائداً (overloaded) لبناء كائنات بقيم ابتدائية مختلفة، كما في Circle() وCircle(double). وإن لم يُعرَّف أي باني، توفّر اللغة ضمنياً بانياً عاماً بلا وسطاء وجسمه فارغ، اسمه default constructor، ولا يُضاف إلا إذا لم يُكتب أي باني صراحة؛ وصنف Circle في القسم السابق كتب بانيين، فلا يوجد ثالث ضمني.
محددات الوصول. الصنف public مرئي لكل الحزم، والصنف بلا محدد (package-private) مرئي داخل حزمته فقط، حتى من ملف آخر في الحزمة نفسها. وعلى الأعضاء:
| المحدد | من يراه |
|---|---|
public | العالم كله: داخل الصنف وخارجه |
protected | الحزمة وكل الأصناف الفرعية |
default (بلا كلمة) | الحزمة فقط |
private | الصنف نفسه فقط، عبر دواله |
وجدول الرؤية الكامل كما في الشريحة:
| موقع الكود | default | public | protected | private |
|---|---|---|---|---|
| الصنف نفسه | نعم | نعم | نعم | نعم |
| صنف في الحزمة نفسها | نعم | نعم | نعم | لا |
| صنف فرعي في الحزمة نفسها | نعم | نعم | نعم | لا |
| صنف فرعي خارج الحزمة | لا | نعم | نعم | لا |
| ليس فرعياً وخارج الحزمة | لا | نعم | لا | لا |
ورسم الشريحة يضع في الحزمة الأولى color وshape وrectangle وcircle وsquare، وفي الثانية number وletter وEnglish وArabic وصنفًا فرعيًا. والقراءة العملية: الفرع خارج الحزمة يرى public وprotected فقط.
default constructor دائمًا؟ لا، فقط عند غياب أي باني مكتوب. وفخ ثانٍ: عضو بلا محدد ليس public بل package-private.circle.radius = 5; من main؟private، والوصول إليه من خارج الصنف ممنوع ولو كان main في الحزمة نفسها؛ والطريق الصحيح setRadius(5).لماذا يهم. كل خطأ وصول في الاختبار يعود إلى هذا الجدول: private لا يعبر حدّ الصنف، وdefault لا يعبر حدّ الحزمة، وprotected يعبر إلى الفرع خارجها.
جدول يجمع مصطلحات الفصل في مكان واحد للمراجعة السريعة.
| المصطلح | المعنى بالعربية | ملاحظة / مثال |
|---|---|---|
OOP | برمجة كائنية | نموذج كائنات تتفاعل |
Object | كائن | هوية وحالة وسلوك |
Class | صنف | قالب بلا قيم كائن |
Instance | نسخة | كائن مُنشأ من الصنف |
Attribute / field | خاصية / حقل | radius، fuel |
Method | دالة مرتبطة بكائن | getArea() تُستدعى على كائن |
State | حالة | قيم الحقول الحالية |
Behavior | سلوك | الأفعال المعرّفة بالدوال |
Message | رسالة | طلب تنفيذ فعل بين كائنات |
Instantiation | إنشاء نسخة | يتم عبر new |
Model | نموذج | تبسيط ووصف من منظور واحد |
Functional requirements | متطلبات وظيفية | ماذا يفعل النظام |
Nonfunctional requirements | متطلبات غير وظيفية | كيف يكون؛ quality attributes |
EER | نموذج كيانات موسّع | لقاعدة علائقية؛ بلا سلوك |
Entity type | نوع كيان | لبنة بنية البيانات في EER |
Entity instance | ظهور واحد لكيان | يقابل Object |
Relationship | ارتباط | يقابل Association |
Association | دور الكائن في ارتباط | مقابل Relationship |
UML | لغة نمذجة موحّدة | name: type و+ و- |
Encapsulation | تغليف | حالة خاصة وواجهة عامة |
Information hiding | إخفاء معلومات | مرادف تقييد الوصول الداخلي |
Abstraction | تجريد | إظهار الجوهر وإخفاء الزائد |
Inheritance | وراثة | نقل خصائص الأب إلى الابن |
Polymorphism | تعدد أشكال | دالة واحدة وسلوك يختلف حسب الصنف |
Overloading | تحميل زائد | بانيان بالاسم نفسه ووسطاء مختلفين |
Constructor | بانٍ | اسم الصنف، بلا إرجاع، مع new |
Default constructor | الباني الافتراضي | عام، بلا وسطاء، فقط إن لم يُكتب باني |
main class | الصنف الرئيسي | الذي يحوي main ويُشغَّل |
Access modifier | محدد وصول | public protected default private |
أخطاء شائعة.
circle2.setRadius(100) يغيّر circle1؛ والصواب أن القيم في الكائن والكائنات مستقلة.void للباني أو إرجاع قيمة منه؛ والصواب: لا نوع إرجاع مطلقًا.default constructor بعد كتابة Circle(double)؛ والصواب أنه يختفي فور وجود أي باني.2 * radius * Math.PI؛ والصواب أنها للمحيط.balance أو radius من خارج الصنف وهما private؛ والصواب المرور بدالة عامة.abstraction مع encapsulation؛ الأول اختيار الجوهر، والثاني حماية الحالة وفرض الواجهة.EER؛ والصواب أنه يورّث الخصائص فقط.Circle لأنه «البرنامج»؛ والصواب أن التشغيل لصنف فيه main.public؛ والصواب package-private.quality attributes.encapsulation وabstraction وinheritance وpolymorphism، مع تعريف الشريحة بسطر ومثال لكل واحد.OO مقابل EER، خصوصاً أن EER يورّث الخصائص ولا يمثّل السلوك.UML: الاسم ثم الحقول ثم البواني والدوال، ومعنى - و+، وترميز الكائن circle1: Circle.default constructor، والتحميل الزائد في Circle() وCircle(double).encapsulation عن abstraction.private لا يعبر حدّ الصنف، وdefault لا يعبر حدّ الحزمة، وprotected يعبر إلى الفرع خارجها.TestCircle: تغيير circle2 لا يمسّ circle1 ولا circle3.Chapter 2 · 33 slides
Objects and Classes
ينتقل الفصل من إنشاء الكائن واستدعاء دواله إلى تنظيم أعضائه: أيّها يخصّ الكائن وأيّها يخصّ الصنف كله، ومن يرى ماذا، وكيف تُحمى الحقول، وكيف ينتقل الكائن إلى دالة، وكيف يسمّي الكائن نفسه.
لكل كائن نسخته الخاصة من instance variable، ولا تُستدعى instance method إلا على كائن موجود عبر متغير مرجع. أما العضو المعلَن بـstatic فنسخة واحدة في الذاكرة يشترك فيها كل كائنات الصنف، ويُستخدم باسم الصنف قبل وجود أي كائن أو بعده. ويجوز الوصول إليه عبر مرجع كائن أيضاً (c1.numberOfObjects) لأنه مشروع، لكن المستحسن باسم الصنف لأنه ليس ملك كائن بعينه.
static: معدّل يربط العضو بالصنف لا بالمثيل، ويُسطَّر في UML للدلالة عليه. والمثيل instance كائن واحد ناتج عن instantiate.
خانة واحدة في الذاكرة للصنف كله؛ تعديلها من أي كائن يظهر عند الجميع.
مثل static final int MAX_RADIUS = 100;؛ يُقرأ باسم الصنف ولا يُعاد إسناده.
غير مربوطة بكائن، تُستدعى باسم الصنف، ولا تعتمد على بيانات كائن بعينه.
مخطط الصنف في UML يكشف الفرق بالتسطير:
Circle ------------------------------------ radius : double numberOfObjects : int <- static (underlined) ------------------------------------ getArea() : double getNumberOfObjects() : int <- static (underlined)
| الوجه | العضو المثيلي instance | العضو الثابت static |
|---|---|---|
| عدد النسخ | نسخة لكل كائن | نسخة واحدة للصنف كله |
| متى يوجد | بعد new فقط | قبل أي كائن وبعده |
| الاستدعاء | c1.getArea() | Circle.getNumberOfObjects() (أو عبر مرجع، والأفضل باسم الصنف) |
| في UML | بلا تسطير | مسطَّر |
public class CircleWithStaticMembers {
double radius;
static int numberOfObjects = 0;
CircleWithStaticMembers() {
radius = 1;
numberOfObjects++;
}
CircleWithStaticMembers(double newRadius) {
radius = newRadius;
numberOfObjects++;
}
static int getNumberOfObjects() { return numberOfObjects; }
double getArea() { return radius * radius * Math.PI; }
}
البانيان يزيدان العداد المشترك، وradius خانة خاصة بكل كائن.
// مقتطف: يُكتب داخل main
CircleWithStaticMembers c1 = new CircleWithStaticMembers(); // radius 1.0
CircleWithStaticMembers c2 = new CircleWithStaticMembers(5); // radius 5.0
c1.radius = 9; // لا يمس c2
System.out.println(CircleWithStaticMembers.numberOfObjects); // 2
العضو المثيلي لا يُقرأ إلا عبر مرجع، والثابت يُقرأ باسم الصنف أو عبر مرجع، والأفضل باسمه لأنه ليس ملك كائن.
Before creating objects The number of Circle objects is 0 After creating c1 c1: radius (1.0) and number of Circle objects (1) After creating c2 and modifying c1 c1: radius (9.0) and number of Circle objects (2) c2: radius (5.0) and number of Circle objects (2)
c1.radius لا يغيّر c2.radius، أما العداد فيبدأ 0 ثم 1 ثم 2 ويظهر بالقيمة نفسها من الكائنين.لماذا يهم: العدادات والثوابت ودوال الأدوات مثل Math.pow مكانها الصنف، وبيانات الكائن مثل نصف القطر مكانها المثيل؛ فموضع الإعلان هو ما يحدد أهذا العضو مشترك أم خاص بكائن واحد.
c1 وc2 ثم c1.radius = 9، ماذا يطبع CircleWithStaticMembers.numberOfObjects؟2. التعديل طال خانة نصف القطر الخاصة بـc1 وحدها، والعداد خانة واحدة مشتركة لم تُعدَّل.الدالة المثيلية تصل إلى الحقول والدوال المثيلية والثابتة معاً، لأن معها كائناً هو this ومعها الصنف. أما الدالة الثابتة فلا كائن مستقبِل لها عند الاستدعاء، فلا تصل إلى عضو مثيلي مباشرةً، بل عبر مرجع كائن صريح مثل a.i وa.m1().
الاستدعاء الثابت: استدعاء يحدث بلا مستقبِل كائن؛ ولهذا يرفض المترجم استخدام عضو مثيلي من سياق static إلى أن يُنشأ كائن ويُستخدم مرجعه.
| من السياق | دالة مثيلية | حقل مثيلي | دالة ثابتة | حقل ثابت |
|---|---|---|---|---|
| داخل دالة مثيلية | نعم | نعم | نعم | نعم |
| داخل دالة ثابتة | لا مباشرةً (نعم عبر a.m1()) | لا مباشرةً (نعم عبر a.i) | نعم | نعم |
public class A {
int i = 5;
static int k = 2;
public static void main(String[] args) {
int j = i; // خطأ: i حقل مثيلي
m1(); // خطأ: m1 دالة مثيلية
}
public void m1() {
i = i + k + m2(i, k); // مشروع: السياق مثيلي
}
static int m2(int i, int j) {
return (int) Math.pow(i, j);
}
}
السطران الممنوعان في main هما قراءة i واستدعاء m1() بلا كائن؛ أما k وm2 فثابتان واستخدامهما مشروع.
التصحيح بإنشاء كائن ثم الوصول من مرجعه:
public static void main(String[] args) {
A a = new A();
int j = a.i; // مشروع
a.m1(); // مشروع
System.out.println(a.i); // 32
}
بعد a.m1() تصبح a.i مساوية 5 + 2 + 25 = 32؛ تتبّع ذهني لا يطبعه البرنامج.
main ثابتة دائماً، فمنها لا تُستخدم حقول الكائن إلا بعد new.لماذا يهم: كل برنامج يبدأ من دالة ثابتة، وفهم الحدّ بين ما تراه الدالة الثابتة وما لا تراه يمنع أخطاء الترجمة المتكررة عند أول استخدام لحقل كائن.
i = i + k + m2(i, k) صحيح داخل m1 وخطأ لو نُقل إلى main؟m1 مثيلية فترى i عبر كائنها. main ثابتة ولا كائن لديها. أما k وm2 فثابتان في الحالين.visibility modifier ويسمّى أيضاً access specifier يحدد من يرى الصنف وأعضاءه. على الصنف: public يجعله متاحاً لأي كود في أي حزمة، وبلا معدّل يبقى محصوراً في حزمته. وعلى العضو: public يُرى من داخل الصنف وخارجه، وprivate من دوال الصنف نفسه فقط، وبلا معدّل يُرى داخل الحزمة كلها.
الرؤية: حكم على مكان الكود لا على «صاحب الكائن»؛ فكود داخل الصنف يرى أعضاءه الخاصة حتى عبر مرجع لكائن آخر من الصنف نفسه.
| المعدّل | داخل الصنف | الحزمة نفسها | حزمة أخرى |
|---|---|---|---|
public | نعم | نعم | نعم |
| بلا معدّل | نعم | نعم | لا |
private | نعم | لا | لا |
مثال الشريحة: الصنف C1 في الحزمة p1 فيه x عام وy بلا معدّل وz خاص، ودوال m1 عامة وm2 بلا معدّل وm3 خاصة. من يصل إلى ماذا من الكائن o:
| العضو | C2 في p1 | C3 في p2 |
|---|---|---|
o.x (public) | نعم | نعم |
o.y (بلا معدّل) | نعم | لا |
o.z (private) | لا | لا |
o.m1() (public) | نعم | نعم |
o.m2() (بلا معدّل) | نعم | لا |
o.m3() (private) | لا | لا |
أي أن C3 تصل إلى o.x وo.m1() فقط. وعلى مستوى الصنف نفسه: class C1 بلا public تراها C2 في p1 ولا تراها C3 في p2، بينما public class C2 تراها C3.
public class C {
private boolean x;
public static void main(String[] args) {
C c = new C();
System.out.println(c.x); // مشروع: الكود داخل C
System.out.println(c.convert()); // -1
}
private int convert() {
return x ? 1 : -1;
}
}
x تبدأ false، فتعيد convert القيمة -1؛ والوصول مشروع لأن الكود داخل الصنف المالك.
public class Test {
public static void main(String[] args) {
C c = new C();
System.out.println(c.x); // خطأ ترجمة
System.out.println(c.convert()); // خطأ ترجمة
}
}
نفس العضوين صارا غير مرئيين لأن الكود خارج الصنف المالك.
private لا يمنع الكائن، بل يمنع الكود الخارجي؛ فـc.x وc.convert() مشروعان داخل C وخطأ ترجمة في Test.لماذا يهم: إخفاء التفاصيل يبدأ من هنا، والخطأ الشائع في التتبع معاملة private كأنها حظر مطلق حتى على دوال الصنف نفسه.
C3 في p2 الدالة m2؟ وهل يقرأ كود C الحقل c.x وهو خاص؟m2، فوصولها افتراضي لا يعبر الحزم. نعم لـc.x، لأن الكود داخل C.جعل الحقول private يحميها ويسهّل صيانة الصنف: العميل لا يعتمد على تمثيل الحقل الداخلي، والصنف يرفض القيم غير المقبولة في مكان واحد. من يحتاج القراءة يأخذ getter، ومن يحتاج التعديل يأخذ setter.
getter / accessor: public returnType getPropertyName()، وإن كان النوع boolean فالعُرف public boolean isPropertyName(). setter / mutator: public void setPropertyName(dataType value). وفي UML الإشارة - تعني private و+ تعني public.
تعيد قيمة الحقل دون كشف موقعه أو طريقة تخزينه، مثل getRadius() وisEmpty().
تفحص القيمة قبل إسنادها؛ فهي نقطة التحقق الوحيدة، مثل رفض نصف القطر السالب.
CircleWithPrivateDataFields ----------------------------------------------- - radius: double - numberOfObjects: int <- private + static ----------------------------------------------- + Circle() + Circle(newRadius: double) + getRadius(): double + setRadius(newRadius: double): void + getNumberOfObjects(): int <- static + getArea(): double
public class CircleWithPrivateDataFields {
private double radius = 1;
private static int numberOfObjects = 0;
public CircleWithPrivateDataFields() {
numberOfObjects++;
}
public CircleWithPrivateDataFields(double newRadius) {
radius = newRadius;
numberOfObjects++;
}
الحقلان خاصان: radius لكل دائرة، والعداد مشترك بين الجميع.
public double getRadius() {
return radius;
}
public void setRadius(double newRadius) {
radius = (newRadius >= 0) ? newRadius : 0;
}
public static int getNumberOfObjects() {
return numberOfObjects;
}
public double getArea() {
return radius * radius * Math.PI;
}
}
setRadius لا يسند القيمة كما هي: السالب يُستبدل بـ0.
// مقتطف: يُكتب داخل main
CircleWithPrivateDataFields myCircle =
new CircleWithPrivateDataFields(5);
System.out.println("The area of the circle of radius "
+ myCircle.getRadius() + " is " + myCircle.getArea());
myCircle.setRadius(myCircle.getRadius() * 1.1);
System.out.println("The area of the circle of radius "
+ myCircle.getRadius() + " is " + myCircle.getArea());
System.out.println("The number of objects created is "
+ CircleWithPrivateDataFields.getNumberOfObjects());
العميل لا يكتب myCircle.radius، بل يمرّ عبر الدوال العامة.
The area of the circle of radius 5.0 is 78.53981633974483 The area of the circle of radius 5.5 is 95.03317777109125 The number of objects created is 1
setRadius(-3) يجعل نصف القطر 0. ولو كان الحقل عاماً لكتب أي عميل myCircle.radius = -3 وضاع فحص الصحة. وتوقيع getter لحقل boolean empty هو public boolean isEmpty().لماذا يهم: تغيير شرط صحة نصف القطر يتم داخل setRadius وحده دون تعديل أي عميل، وهذا هو مكسب التغليف العملي.
(newRadius >= 0) من setRadius؟private.تمرير كائن إلى دالة هو تمرير مرجعه لا الكائن نفسه. والأسلوب pass-by-value ينسخ القيمة الممرَّرة: إن كانت عدداً نُسخ العدد، وإن كانت مرجعاً نُسخت نسخة من العنوان، ويبقى الكائن واحداً على الـheap.
activation record: إطار الدالة على الـstack، يحوي معاملاتها ومتغيراتها المحلية؛ في المخطط إطار printArea فيه times نسخة مستقلة، وc نسخة من المرجع تشير إلى كائن main نفسه.
العدد الممرَّر نسخة؛ تغييره داخل الدالة لا يمس متغير المستدعي.
العنوان يُنسخ، والكائن واحد؛ تعديل حقوله يظهر عند المستدعي.
c = ... يغيّر النسخة المحلية فقط، ولا يعيد ترتيب متغيرات المستدعي.
stack heap -------------------------- -------------------------- main frame Circle object (@A) n = 5 radius = 1.0 myCircle = @A printArea frame times = 5 (copy of n) c = @A (copy of the reference)
public class Test {
public static void main(String[] args) {
Circle circle1 = new Circle(1);
Circle circle2 = new Circle(2);
swap1(circle1, circle2);
System.out.println("After swap1: circle1 = "
+ circle1.radius + " circle2 = " + circle2.radius);
swap2(circle1, circle2);
System.out.println("After swap2: circle1 = "
+ circle1.radius + " circle2 = " + circle2.radius);
}
الوصول إلى radius ممكن لأنه بلا معدّل والصنفان في الحزمة الافتراضية نفسها.
public static void swap1(Circle x, Circle y) {
Circle temp = x;
x = y;
y = temp;
}
public static void swap2(Circle x, Circle y) {
double temp = x.radius;
x.radius = y.radius;
y.radius = temp;
}
}
class Circle {
double radius;
Circle(double newRadius) {
radius = newRadius;
}
}
swap1 يبدّل نسخ المراجع المحلية فلا يتغير شيء في main؛ swap2 يبدّل الحقول داخل الكائنين فيظهر التبديل.
After swap1: circle1 = 1.0 circle2 = 2.0 After swap2: circle1 = 2.0 circle2 = 1.0
1.0 ثم 2.0، والثاني 2.0 ثم 1.0. القاعدة: تعديل حقول الكائن يظهر، وتعديل المعامل نفسه لا يظهر.لماذا يهم: أي دالة تستقبل كائناً وتعدّل حقوله تعدّل كائن المستدعي، وهذا يفسّر كثيراً من النتائج التي تبدو غير متوقعة في أسئلة التتبع.
swap2 بـCircle temp = x; x = y; y = temp; فما الناتج؟1.0 و2.0 كما بعد swap1، لأن التبديل طال نسخ المراجع المحلية لا حقول الكائنات.this The this Referencethis يشير إلى الكائن الذي استُدعيت عليه الدالة المثيلية. وthis.radius وradius متكافئان ما دام اسم الحقل غير محجوب، أما الاستعمال الحاسم فتمييز حقل الصنف من وسيط يحمل الاسم نفسه.
this: مرجع ثابت نحو المستقبِل الحالي؛ يصلح لقراءة حقول الكائن واستدعاء دواله مثل this.getArea()، ولا معنى له في دالة static لأنه لا كائن مستقبِل في سياق static.
public class Circle {
private double radius;
public double getArea() {
return this.radius * this.radius * Math.PI;
}
public String toString() {
return "radius: " + this.radius + ", area: " + this.getArea();
}
}
الشريحة كتبت toString بلا فاصل قبل "area: " فيلتصق الناتج؛ الفاصل هنا توضيحي.
الصورة المكافئة تحذف this ما دام الحقل غير محجوب: return radius * radius * Math.PI; و", area: " + getArea().
public class Circle {
private double radius;
Circle() {
radius = 1;
}
Circle(double radius) {
this.radius = radius; // لا تكتب radius = radius
}
}
عند تطابق الاسم يعني الاسم المجرّد الوسيط، فتبقى قيمة الحقل الابتدائية 0.0 رغم تمرير عدد.
new Circle(5) تكون radius صفراً إذا كُتب radius = radius، وخمسة إذا كُتب this.radius = radius؛ لأن الإسناد الأول ينقل الوسيط إلى نفسه.لماذا يهم: التتبع الذي يساوي ناتج الباني بالوسيط الممرَّر يفشل إن غاب this ويصيب إن وُجد، وهو أول ما يُختبر من أثر الحجب.
this.radius = radius داخل دالة static في الصنف نفسه؟this يحتاج كائناً مستقبِلاً، والدالة الثابتة تُستدعى بلا كائن، فيرفض المترجم ذلك.| المصطلح | المعنى بالعربية | ملاحظة / مثال |
|---|---|---|
instance variable | متغير مثيلي، نسخة لكل كائن | radius |
instance method | دالة تُستدعى على كائن | getArea() |
static variable | متغير واحد مشترك بين كل كائنات الصنف | numberOfObjects |
static method | دالة بلا كائن مستقبِل | getNumberOfObjects() |
static constant | ثابت مشترك static final | لا يُعاد إسناده |
UML underline | تسطير العضو الثابت | تحت numberOfObjects |
visibility modifier | معدّل رؤية العضو أو الصنف | public وprivate والافتراضي |
package | حزمة تجمع أصنافاً | الوصول الافتراضي لا يعبر إلى حزمة أخرى |
data field encapsulation | إخفاء الحقل خلف دوال | الحقل - والدوال + |
getter / accessor | دالة قراءة | getRadius() أو isEmpty() |
setter / mutator | دالة تعديل | setRadius يرفض السالب |
pass-by-value | نسخ القيمة الممرَّرة | العدد يُنسخ، والمرجع يُنسخ |
reference | عنوان الكائن على الـheap | نسختان تشيران إلى كائن واحد |
activation record | إطار الدالة على الـstack | يحوي المعاملات والمتغيرات المحلية |
this | مرجع الكائن الحالي | يكشف الحقل المحجوب |
hidden data field | حقل حجبه وسيط بالاسم نفسه | this.radius = radius |
| الخطأ | الصواب |
|---|---|
استدعاء getArea() أو قراءة radius من main بلا كائن | إنشاء كائن ثم c.getArea()، أو الاكتفاء بثابت مثل numberOfObjects |
ظن أن c1.numberOfObjects وc2.numberOfObjects نسختان | خانة واحدة، وتعديل العداد يظهر للكل |
كتابة obj.secret من صنف آخر والحقل خاص | دالة عامة في الصنف المالك، أو نقل الكود داخل الصنف |
ظن أن private يمنع حتى c.x داخل الصنف نفسه | المنع على الكود الخارجي فقط، كما في C مقابل Test |
معاملة العضو بلا معدّل كأنه public عبر الحزم | C3 في p2 لا ترى y ولا m2 ولا صنفاً غير عام |
اعتقاد أن swap1 يبدّل كائنات main | التبديل محلي للنسخ، وswap2 وحده يبدّل لأن التعديل طال الكائن |
نسيان this عند تطابق الأسماء، أو استخدامه في دالة ثابتة | this.radius = radius في الباني المثيلي فقط |
setter يسند أي قيمة فيُلغى نفع التغليف | فحص مثل (newRadius >= 0) ? newRadius : 0 |
CircleWithStaticMembers: العداد من 0 إلى 1 إلى 2، وتعديل c1.radius إلى 9 لا يمس c2.public والافتراضي وprivate داخل الصنف والحزمة وخارجها، وعليه مثال C1 وC2 وC3.private مسموح داخل الصنف المالك وممنوع في Test، مع توقيعي get وis وset.swap1 يُبقي 1.0 و2.0، وswap2 يجعلهما 2.0 و1.0.this مع الحقل المحجوب، ومخطط stack/heap: نسختا المرجع تشيران إلى كائن واحد، ونسختا العدد مستقلتان.Chapter 3 · 60 slides
Inheritance and Polymorphism
الابن يرث من أب واحد بـextends، ويخصّص بوراثة الأعضاء أو بإعادة تعريف الدوال، ثم يقرّر نوع الكائن الفعلي — لا نوع المرجع — أي نسخة تُنفَّذ.
Inheritance: اشتقاق صنف جديد من صنف موجود، فيرث بياناته ودواله ويضيف أو يعدّل. الصنف الموجود superclass (أو parent class / base class)، والمشتق subclass (أو child class).
دائرة ومستطيل يتشاركان اللون والامتلاء وتاريخ الإنشاء، ويختلفان في نصف القطر أو في العرض والارتفاع. نضع المشترك في Shape، ونشتق منه Circle وRectangle بدل نسخ الحقول في كل صنف.
الوراثة السليمة علاقة is-a: الدائرة شكل، وليست «الشكل دائرة». في مخطط UML (الشريحتان 4 و6) يظهر الأب أعلى الابن، والسهم المثلث يتجه من الابن إلى الأب.
class Vehicle {
protected String plate;
public void start() { System.out.println("Engine on"); }
}
class Car extends Vehicle {
private int doors; // إضافة الابن
}
الابن لا يعيد كتابة plate ولا start؛ يرثهما ويضيف ما يخصّه.
لماذا يهم: إعادة استخدام الشفرة وتخصيصها في مكان واحد. تعديل دالة في الأب ينعكس على كل الأبناء، وتصحيح الخطأ يتم مرة واحدة.
Pickup يمتد من Car وTruck؟Pickup من أب واحد، ويأخذ السلوك المشترك من واجهة.extends: الكلمة المحجوزة التي تربط الابن بالأب. بواني الأب لا تُورَّث؛ تُستدعى صراحةً بـsuper أو ضمناً عند البناء.
Shape يخزّن color وfilled وdateCreated، ويوفّر بانياً بلا وسطاء وبانياً باللون والامتلاء. الحقول private، والقيمة الافتراضية للون "white"، والشكل لا يُملأ حتى يُضبط.
public class Shape {
private String color = "white";
private boolean filled;
private java.util.Date dateCreated;
public Shape() {
dateCreated = new java.util.Date();
}
public Shape(String color, boolean filled) {
dateCreated = new java.util.Date();
this.color = color;
this.filled = filled;
}
public void setColor(String color) { this.color = color; }
public String getColor() { return color; }
public void setFilled(boolean filled) { this.filled = filled; }
public boolean isFilled() { return filled; }
public java.util.Date getDateCreated() { return dateCreated; }
@Override
public String toString() {
return "created on " + dateCreated + "\ncolor: " + color + " and filled: " + filled;
}
}
جالب boolean يُسمّى isFilled لا getFilled (اصطلاح الشرائح)، وgetColor وgetDateCreated وtoString من الشريحة 9 نفسها؛ فعليها يعتمد printCircle وsuper.toString() في القسم 3.5.
public class Circle extends Shape {
private double radius;
public Circle() { }
public Circle(double radius) {
this.radius = radius;
}
public Circle(double radius, String color, boolean filled) {
this.radius = radius;
setColor(color); // color خاص في الأب
setFilled(filled);
}
}
الحقل الخاص لا يُرى من الابن، فيصل إليه عبر الدوال الموروثة setColor وsetFilled.
Rectangle يضيف width وheight مع المساحة والمحيط، وبانيه يأخذ العرض والارتفاع ثم اللون والامتلاء. في UML الإشارة - خاص و+ عام.
لماذا يهم: التخصيص يكون بالدوال الموروثة لا بالوصول المباشر؛ لذلك يبقى تغليف الأب سليماً.
Circle الباني Shape(String, boolean)؟new Circle(2.0, "red", true) ينفّذ باني Circle الذي ينادي باني الأب.super(...): استدعاء باني الأب، ويجب أن يكون أول جملة في باني الابن. غياب الاستدعاء الصريح يعني super() ضمنياً، بشرط وجود باني أب بلا وسطاء.
إن لم يُعرّف الأب أي بانٍ، أدرج المترجم باني default فارغاً فينجح الاستدعاء الضمني. إن عرّف الأب بواني بوسطاء فقط فلا باني فارغ، ويفشل super() الضمني بخطأ ترجمة.
| الشفرة | الحكم | السبب |
|---|---|---|
الأب فيه Shape() والابن Circle() بلا super | صحيح | تعادل super(); |
Circle(double radius) يسند نصف القطر فقط | صحيح | super(); ثم الإسناد |
الأب فيه Shape(String, boolean) فقط والابن Circle() | خطأ | لا باني فارغ يُستدعى |
الأب بلا أي بانٍ والابن Circle() | صحيح | باني default في الأب |
أول سطر في الابن super("Red", true); | صحيح | استدعاء صريح مطابق |
Circle بلا بوانٍ والأب فيه Shape() | صحيح | الباني المُدرج يستدعي super() |
public Circle() {
super("Red", true); // يجب أن يسبق أي جملة أخرى
}
نداء صريح مطابق لباني الأب بالوسيطين؛ لا حاجة للإسناد اليدوي بعد ذلك.
لماذا يهم: الابن مسؤول عن بناء أبيه؛ بانٍ لا يجد باني أب مطابقاً لا يُترجم، وترتيب الجملة الأولى شرط يسبق أي إسناد.
this.radius = radius; قبل super(...) غير مسموح؛ استدعاء باني الأب أول جملة دائماً. ولا يجتمع this(...) وsuper(...) في الباني نفسه.super بعد جملة.Shape فيه باني واحد بوسطاء، وCircle() فارغ. هل تُترجم؟super() لا يجد باني أب بلا وسطاء. الحل: super(color, filled) صريحاً أو إضافة باني فارغ للأب.Constructor chaining: إنشاء كائن يستدعي بواني كل الأجداد على سلسلة الوراثة، من الأعلى إلى الأسفل، قبل أن يعمل جسم باني الابن.
عند دخول بانٍ يبدأ بـthis(...) ينتقل إلى بانٍ آخر في الصنف نفسه ولا يستدعي الأب بعد؛ والباني الذي لا يبدأ بـthis يبدأ بـsuper صريحاً أو ضمنياً.
public class Faculty extends Employee {
public static void main(String[] args) {
new Faculty();
}
public Faculty() {
System.out.println("(4) Faculty's no-arg constructor is invoked");
}
}
class Employee extends Person {
public Employee() {
this("(2) Invoke Employee's overloaded constructor");
System.out.println("(3) Employee's no-arg constructor is invoked");
}
public Employee(String s) {
System.out.println(s);
}
}
Employee() يبدأ بـthis(String)، فيؤجّل نداء الأب إلى الباني الآخر.
class Person {
public Person() {
System.out.println("(1) Person's no-arg constructor is invoked");
}
}
جذر السلسلة: ينفّذ أولاً لأنه لا أب له فوقه.
ترتيب النداء ثم الطباعة (خطوات الشرائح 19–27):
main ثم new Faculty().Faculty() وفي أوله super() نحو Employee().Employee() يبدأ بـthis(String) لا بـsuper.Employee(String) يستدعي ضمنياً Person().Person ثم الرجوع.Employee(String).Employee() وطباعة السطر (3).Faculty() وطباعة السطر (4).المخرجات:
(1) Person's no-arg constructor is invoked (2) Invoke Employee's overloaded constructor (3) Employee's no-arg constructor is invoked (4) Faculty's no-arg constructor is invoked
لماذا يهم: جسم الباني لا يعمل قبل اكتمال بناء الأب، وأي طباعة في الابن تظهر بعد طباعات الأجداد.
this ظاهرة قبل أي طباعة في Employee()؟Employee(String) ينادي Person() أولاً فيُطبع (1)، ثم يُطبع الوسيط (2)، ثم يعود فيُطبع (3)، ثم (4).الابن يصنع أربع حركات: يرث أعضاء الأب، ويضيف حقولاً (radius)، ويضيف دوالاً (getArea وgetDiameter وgetPerimeter وprintCircle)، ويعيد تعريف دالة الأب (toString).
Method overriding: دالة غير ساكنة (instance method) في الابن تطابق اسم دالة الأب وأنواع وسطائها. التعليق @Override يعلن النية؛ فإن لم توجد دالة مطابقة فشل الترجمة.
public class Circle extends Shape {
private double radius;
public double getRadius() { return radius; }
public void setRadius(double radius) { this.radius = radius; }
public double getArea() { return radius * radius * Math.PI; }
public double getDiameter() { return 2 * radius; }
public double getPerimeter() { return 2 * radius * Math.PI; }
}
حقل جديد ودوال جديدة تُضاف إلى ما ورثه Circle من Shape.
public void printCircle() {
System.out.println("The circle is created " + getDateCreated()
+ " and the radius is " + radius);
}
@Override
public String toString() {
return "The circle is created " + super.toString()
+ " and the radius is " + radius;
}
}
super.toString() يضم نص الأب بدل تكراره؛ ونسخة أخرى على الشريحة تبني النص من getDateCreated() دون super.toString().
super.method() تستدعي نسخة الأب من دالة أُعيد تعريفها، وsuper يصل إلى الحقول والدوال العامة أو protected فقط.
| الوجه | إعادة التعريف overriding | التحميل الزائد overloading |
|---|---|---|
| التوقيع | مطابق تماماً | وسطاء مختلفة |
| المكان | بين أب وابن | في الصنف نفسه غالباً |
| وقت الاختيار | وقت التشغيل حسب الكائن | وقت الترجمة حسب الوسائط |
| الأثر | يستبدل نسخة الأب | تبقى النسختان |
مثال الشريحة: الصنف B فيه p(double) تطبع i * 2، والابن A يعرّف إمّا p(double) أو p(int). الاستدعاءان a.p(10) وa.p(10.0):
| البند | الابن يعرّف p(double) | الابن يعرّف p(int) |
|---|---|---|
| العلاقة | إعادة تعريف لنسخة الأب | تحميل زائد؛ نسخة الأب باقية |
a.p(10) | توسيع إلى double ثم نسخة الابن: 10.0 | تطابق p(int): 10 |
a.p(10.0) | نسخة الابن: 10.0 | نسخة الأب: 20.0 |
class B {
public void p(double i) { System.out.println(i * 2); }
}
class A extends B {
@Override
public void p(double i) { System.out.println(i); } // إعادة تعريف
}
class Test {
public static void main(String[] args) {
A x = new A();
x.p(10); // 10.0
x.p(10.0); // 10.0
}
}
التوقيع نفسه، فالنسخة المنفَّذة نسخة الابن في الحالتين.
class B {
public void p(double i) { System.out.println(i * 2); }
}
class A extends B {
public void p(int i) { System.out.println(i); } // تحميل زائد، لا @Override
}
class Test {
public static void main(String[] args) {
A x = new A();
x.p(10); // 10
x.p(10.0); // 20.0
}
}
class B مكرّرة هنا كما في البطاقة السابقة ليُترجم الملف وحده. int يطابق نسخة الابن، وdouble لا يجد إلا نسخة الأب.
10.0 ثم 10.0، والتحميل الزائد يعطي 10 ثم 20.0. الاستدعاء في main واحد، والمخرجات مختلفة.private لا تُرى خارج صنفها، فدالة الابن بالاسم نفسه دالة جديدة لا علاقة لها بها. والدالة static تُورَّث ولا تُعاد تعريفها؛ تعريفها في الابن hiding أي إخفاء للنسخة الأصلية لا استبدال لها، والاختيار يتبع نوع المرجع لأن الربط ساكن.class P { public static void m() { System.out.println("P"); } }
class C extends P { public static void m() { System.out.println("C"); } }
class Test {
public static void main(String[] args) {
P ref = new C();
ref.m(); // P: النسختان موجودتان والاختيار بنوع المرجع
}
}
لو كانت m غير ساكنة لطُبع C؛ السكون يمنع الربط الديناميكي.
لماذا يهم: الخلط بين التحميل وإعادة التعريف يغيّر المخرجات مع أن الاستدعاء في main واحد، والفرق كله في التوقيع وفي نوع الكائن.
p(int) في الابن تلغي p(double) في الأب؟10 يختار int في الابن و10.0 يختار نسخة الأب.Access modifiers: من الأضيق إلى الأوسع private ثم default (بلا كلمة) ثم protected ثم public. الرمز # في UML يعني protected.
| المعدّل | الصنف | الحزمة | ابن (الحزمة نفسها) | ابن (حزمة أخرى) | صنف غير ابن في حزمة أخرى |
|---|---|---|---|---|---|
public | Yes | Yes | Yes | Yes | Yes |
protected | Yes | Yes | Yes | Yes | No |
default | Yes | Yes | Yes | No | No |
private | Yes | No | No | No | No |
عمودا «الحزمة» و«صنف غير ابن في حزمة أخرى» يقيسان الوصول من صنف غير ابن: الأول داخل الحزمة والثاني خارجها، فـprotected يقول Yes في الحزمة وNo خارجها بلا تناقض.
تطبيق الشريحة على C1 في الحزمة p1: x عام، y محمي، z بلا معدّل، u خاص، وm() محمية.
| الصنف | يصل إلى |
|---|---|
C2 في p1 وليس ابناً | o.x وo.y وo.z وo.m()، لا o.u |
C3 extends C1 داخل p1 | x وy وz وm()، لا u |
C4 extends C1 في p2 | x وy وm()، لا z ولا u |
C5 في p2 وليس ابناً | o.x فقط |
دقة جافا: الابن في حزمة أخرى يصل إلى العضو المحمي عبر كائن من نوعه هو (this أو مرجع من نوع الابن)، لا عبر مرجع من نوع الأب؛ ولذلك o.y حيث o من نوع C1 داخل C4 خطأ ترجمة، بخلاف this.y.
default الحزمة فقطصنف أو عضو بلا معدّل لا يراه أحد خارج الحزمة. في المثال الصنف Addition عام داخل abcpackage لكن addTwoNumbers بلا معدّل، فـTest في xyzpackage ينشئ الكائن ثم تفشل الترجمة عند النداء برسالة أن الدالة غير مرئية.
private الصنف فقطالحقل والدالة مرئيان داخل الصنف فقط، ولا يُعلَن صنف أو واجهة من المستوى الأعلى private. الباني الخاص يمنع إنشاء الكائن من خارج الصنف. في المثال ABC.num الخاص وsquare الخاصة، والمترجم يقول The field ABC.num is not visible.
protected الحزمة والأبناءيُوضع على بيانات ودوال داخل صنف، لا على الصنف نفسه. يصل إليه أي صنف في الحزمة وأي ابن ولو في حزمة أخرى. الابن Test في xyzpackage ينادي addTwoNumbers(11, 22) على كائن Test فيطبع 33.
public أي مكانالعضو والصنف يُريان من أي حزمة ومن أي صنف. addTwoNumbers(100, 1) من حزمة أخرى يطبع 101.
protected void print() في Shape تصبح public void print() في Circle: صحيح. جعلها private: خطأ، لأن الابن لا يضيّق ما ضمنه الأب.لماذا يهم: protected هو معدّل الوراثة: الابن في حزمة أخرى يرى العضو، والصنف الغريب في تلك الحزمة لا يراه. والإضعاف يكسر من كان يستدعي الدالة عبر مرجع الأب.
C1 يراها؟x والمحمي y وm()، لا الافتراضي z ولا الخاص u — وهو موضع C4 في الجدول.final يجمد ثلاثة أشياء مختلفة، وكل صنف في جافا سليل java.lang.Object؛ فإن لم تُكتب extends فالأب هو Object.
قيمته ثابتة لا تُسند بعد التهيئة: final static double PI = 3.14159;
لا يُمدَّد: final class Math. أي محاولة extends Math خطأ ترجمة.
لا يعيد الابن تعريفها؛ حماية سلوك لا يجوز تغييره في السلسلة.
toString(): تُرجع تمثيلاً نصياً للكائن. التنفيذ الافتراضي في Object: اسم الصنف ثم @ ثم عدد يمثل الكائن.
// مقتطف: يُكتب داخل main
Loan loan = new Loan();
System.out.println(loan.toString());
System.out.println(loan); // تستدعي toString ضمنياً
المخرج على الشريحة: Loan@15037e5؛ السطران يطبعان النص نفسه.
class Circle { } // الأب الفعلي: Object
// الصورة المكافئة نفسها، ولا تُكتب مع السطر السابق:
// class Circle extends Object { }
الشكلان متكافئان؛ الشكل الأول لا يلغي وجود الأب الضمني.
println على كائن بلا toString خاصة يطبع صيغة Class@hash لا محتوى الحقول؛ والمطلوب عادة إعادة تعريفها كما فعل Shape وCircle.لماذا يهم: إعادة تعريف toString قرار مقروئية وأداة تنقيح، وfinal قرار تصميم يمنع تخصيصاً غير آمن في السلسلة.
toString؟@، ثم عدد يمثل الكائن، مثل Loan@15037e5.Polymorphism: إشارة مرجع الأب إلى كائن ابن، فيُختار عند التشغيل النسخة المعاد تعريفها حسب نوع الكائن لا نوع المرجع. المرجع الذي يبدّل نوع الكائن عبر الزمن polymorphic reference، والربط المؤجَّل dynamic binding.
القط حيوان، فيجوز لمرجع Animal أن يشير إلى كائن Cat؛ كل الحيوانات تصدر صوتاً، لكن لكل نوع صوته.
// مقتطف: يُكتب داخل main
Animal animal = new Cat(); // توسيع
animal.sound(); // صوت القط
animal = new Duck();
animal.sound(); // صوت البط؛ الدالة تتبدل من نداء إلى نداء
Person per = new Student(); // widening: إسناد كائن ابن إلى مرجع جد بلا قسر
Student s1 = new Student();
per = s1; // widening أيضاً
Student s2 = (Student) per; // narrowing، القسر واجب
التوسيع (upcasting) إسناد مباشر؛ التضييق (narrowing) يحتاج قسراً لأن ليس كل Person طالباً.
مصفوفة متعددة الأشكال (polymorphic array): نوع العناصر هو الأب، وكل خانة كائن ابن.
// مقتطف: يُكتب داخل main
Animal[] animals = new Animal[3];
animals[0] = new Cat();
animals[1] = new Dog();
animals[2] = new Wolf();
System.out.println("-----------------------");
animals[0].eat();
animals[0].move();
animals[0].sounds();
تكرار الكتل نفسها للعنصرين animals[1] وanimals[2] بفاصل بينها.
Animal يعرّف eat وmove وsounds بنصوص عامة (Animal can eat وما يقابلها). Cat وDog يعيدان تعريف الثلاث بـ@Override؛ وWolf يعيد تعريف eat وsounds فقط فتبقى move نسخة الأب، ولهذا وُضع على الشريحة تعليق No overriding عند animals[2].move().
public class Wolf extends Animal {
@Override
public void eat() {
System.out.println("I am Wolf, I can eat");
}
@Override
public void sounds() {
System.out.println("I am Wolf, I can make sounds");
}
}
لا تعريف لـmove هنا، فيُورَّث نص الأب كما هو.
المخرجات:
----------------------- I am Cat, I can eat I am Cat, I can move I am Cat, I can make sounds ----------------------- I am Dog, I can eat I am Dog, I can move I am Dog, I can make sounds ----------------------- I am Wolf, I can eat Animal can move I am Wolf, I can make sounds
Animal لا يمنع تنفيذ Cat وDog. والسطر Animal can move دليل أن غياب إعادة التعريف يُبقي نسخة الأب، لا أن النداء «فشل».لماذا يهم: حلقة واحدة على Animal[] تشغّل سلوكاً مختلفاً لكل عنصر دون سؤال عن النوع الفعلي، فتُضاف أنواع جديدة بلا تعديل الحلقة.
Animal a = new Wolf(); a.move(); ماذا يُطبع إن لم يعد Wolf تعريف move؟Animal can move. نوع الكائن ذئب، لكن الدالة غير المعاد تعريفها تبقى للأب.| المصطلح | المعنى بالعربية | ملاحظة أو مثال |
|---|---|---|
inheritance | اشتقاق صنف من صنف | الابن يرث البيانات والدوال |
superclass / parent / base class | الصنف الأب | Shape، Person، Animal |
subclass / child class | الصنف الابن | Circle، Faculty، Cat |
| is-a | الابن نسخة أخص من الأب | Circle هو Shape |
extends | إعلان الوراثة | أب واحد فقط |
| multiple inheritance | وراثة من أكثر من أب | غير مدعومة للأصناف |
super | نداء باني الأب أو عضوه | أول جملة إن نادى الباني |
| constructor chaining | تسلسل بواني الأجداد | Faculty → Employee → Person |
| overriding | استبدال جسم دالة بالتوقيع نفسه | @Override |
| overloading | الاسم واحد والوسطاء مختلفة | تُحسم وقت الترجمة |
| hiding | إخفاء دالة static في الابن | ليست إعادة تعريف؛ النسختان موجودتان والاختيار بنوع المرجع |
protected | مرئي للحزمة وللأبناء | الرمز # |
default | مرئي داخل الحزمة فقط | بلا كلمة معدّل |
final | ثابت، أو صنف لا يُمد، أو دالة لا تُستبدل | PI، Math |
Object | جدّ كل الأصناف | الأب إن غاب extends |
toString | تمثيل نصي للكائن | الافتراضي Loan@15037e5 |
| polymorphic reference | مرجع يبدّل نوع الكائن | Animal animal = new Cat(); |
| widening / upcasting | إسناد كائن إلى مرجع جد | بلا قسر |
| narrowing | إرجاع المرجع إلى نوع الابن | يلزم قسر (Student) |
| dynamic binding | اختيار النسخة وقت التشغيل حسب الكائن | eat للقط لا للأب |
| polymorphic array | مصفوفة أب تحمل أبناء مختلفين | Animal[3] |
super أول جملة، والوصول يُوسَّع لا يُضيَّق، وإعادة التعريف لدالة غير ساكنة مرئية فقط.| الخطأ الشائع | الصواب |
|---|---|
ظن أن بواني الأب موروثة، أو ترك Circle() فارغاً والأب بلا باني فارغ | البواني تُستدعى بـsuper ولا تُنسخ؛ وللحالة الثانية super(color, filled) صريح أو باني أب بلا وسطاء |
كتابة جملة قبل super(...) أو الجمع بين this() وsuper() في الباني نفسه | أحدهما فقط، وفي السطر الأول |
ظن أن الطباعة في Faculty تسبق الأب | (1) ثم (2) ثم (3) ثم (4) |
معاملة p(int) كإعادة تعريف لـp(double) | تحميل زائد، والمخرجات 10 ثم 20.0 |
وضع @Override على دالة خاصة أو ساكنة، أو إضعاف protected إلى private | إعادة التعريف لدالة غير ساكنة مرئية، والوصول يُوسَّع فقط |
نداء addTwoNumbers الافتراضية من حزمة أخرى، أو قراءة num الخاص | public للنداء الحر، أو البقاء داخل الحزمة أو الصنف |
انتظار I am Wolf, I can move مع أن move لم تُعد تعريفها | Animal can move |
| قسر عكسي بلا حاجة، أو إسناد ابن إلى أب مع قسر | التوسيع إسناد مباشر؛ التضييق وحده يحتاج القسر |
| الشرائح | القسم |
|---|---|
| 1–2 | خريطة الفصل وأهدافه داخل الأقسام 1–8 |
| 3–7 | 3.1 الوراثة وعلاقة is-a، بما فيها منع الوراثة المتعددة |
| 8–10 | 3.2 Shape وCircle وextends وباني الابن |
| 11–17 | 3.3 super وأحكام صحّة البواني |
| 18–27 | 3.4 سلسلة البناء وتتبع Faculty |
| 28–36 | 3.5 إضافات الابن، toString، إعادة التعريف مقابل التحميل، super للدوال، قيود الخاص والساكن |
| 37–47 | 3.6 جدول الوصول وأمثلة default وprivate وprotected وpublic |
| 48–49 | 3.6 تقوية الوصول عند إعادة التعريف ومنع إضعافه |
| 50–52 | 3.7 final وObject وtoString |
| 53–57 | 3.8 المراجع، التوسيع والتضييق، المصفوفة متعددة الأشكال |
| 58–59 | 3.8 Animal وCat وDog وWolf ومخرجات الاختبار |
| 60 | ختام العرض؛ لا يضيف قاعدة جديدة |
extends.super() الضمني ومتى يفشل إن غاب الباني الفارغ.Faculty / Employee / Person وفرق this(String) عن super().10.0 و10.0 مقابل 10 و20.0.super.toString() لضم نص الأب.C4 أمام C5، وتقوية الوصول لا إضعافه.final الثلاثة، والأب الضمني Object، وشكل toString الافتراضي.Animal حيث Wolf.move تبقى للأب.Chapter 4 · 33 slides
Abstract Classes and Interfaces
يكمل هذا الفصل الوراثة وpolymorphism: نُغلق النوع العام بالصنف المجرد فلا يُنشأ كائن منه، ونصف السلوك المشترك بالواجهة فتُنفَّذ في أصناف لا تربطها قرابة. النتيجة: مرجع واحد من نوع الأب أو الواجهة يجمع كائنات مختلفة، وكل ابن ملزم بتنفيذ ما يفرضه العقد.
في شجرة الوراثة يصير الأب أعمّ والابن أخصّ. إنشاء كائن من Animal غير منطقي: القط والكلب هما الكائنان الحقيقيان، وAnimal ليس شيئاً تراه في الواقع. المشكلة نفسها مع الطالب: كل طالب إما طالب بكلوريوس أو ماجستير أو دكتوراه، ولا يصح أن ينشئ أحد «طالباً عاماً» لا يمثّل أحداً.
abstract class: صنف مسبوق بالكلمة المفتاحية abstract، يمنع إنشاء كائن منه بـ new، ويبقى قابلاً للوراثة.
لماذا نُبقي الأب إذن؟ لسببين لا ثالث لهما: أن ينفّذ السلوك المشترك مرة واحدة مثل setLogin وgetLogin بدل تكراره في كل ابن، وأن نتعامل مع كل الطلاب عبر النوع Student بـ polymorphism بغضّ النظر عن الابن الفعلي.
لماذا يهم: من دون أب مجرد إما نكرّر الشيفرة في كل ابن، وإما نسمح بكائن «طالب عام» لا يمثّل أحداً. التجريد هنا قرار تصميم لا مجرد قيد لغوي.
Student إن كان ممنوعاً إنشاء كائن منه؟ الجواب المطلوب: لجمع السلوك المشترك في مكان واحد، ولاستخدامه نوعاً يشير إلى أي ابن عبر polymorphism.المنع يخص new فقط. الصنف المجرد ما زال يُورَّث، ويملك حقولاً ودوالاً ذات جسم وبنّائين. الابن غير المسبوق بـ abstract يُسمّى concrete class، مثل PhdStudent.
public abstract class Student {
protected String login, department, name;
public Student() {
login = "";
department = "";
name = "";
}
public void setLogin(String login) {
this.login = login;
}
public String getLogin() {
return login;
}
}
بنّاء الأب مكتوب كالمعتاد، ويُستدعى من بنّاء الابن عبر constructor chaining.
public class PhdStudent extends Student { // concrete class
private String supervisor;
public PhdStudent() {
super(); // يستدعي بنّاء Student
}
public void setSupervisor(String login) {
supervisor = login;
}
}
الشريحة تكتب public على الصنفين لأنهما في ملفين منفصلين (Student.java وPhdStudent.java)، فلا تُخالف قاعدة «نوع public واحد لكل ملف». وتكتب new String(login) في setLogin/getLogin وفي setSupervisor؛ والأبسط this.login = login; وsupervisor = login; والسلوك نفسه لأن String غير قابلة للتغيير.
مثال الفرق: new Student() خطأ ترجمة، بينما new PhdStudent() صحيح ويشغّل بنّاء Student أولاً ثم جسم بنّاء الابن.
لماذا يهم: الصنف المجرد يغلق النوع العام ويُبقي شيفرته للأبناء. حذف البنّاء منه يقطع سلسلة التهيئة، فتصل حقول الأب إلى الابن غير مهيّأة.
منع الإنشاء لا يمنع استخدام الاسم نوعاً لمرجع أو لمصفوفة. العناصر الفعلية تكون من أبناء ملموسين، والمرجع يبقى من نوع الأب.
data type: الصنف المجرد والواجهة يصلحان نوعاً مرجعياً (reference type) لمتغيّر أو مصفوفة أو معامل دالة، ولا يصلحان لإنشاء كائن.
// مقتطف: يُكتب داخل main
Shape[] geo = new Shape[10]; // صحيح: النوع مجرد، وكل عنصر null
geo[0] = new Circle(5); // صحيح: Circle ابن ملموس
geo[1] = new Shape(); // خطأ ترجمة: لا new على الصنف المجرد
المصفوفة نوعها Shape، وعناصرها تبدأ null، ولا يُنشأ أي كائن Shape. الشريحة 18 تستخدم new Circle() اعتماداً على بنّاء بلا معاملات في مخطط الشريحة 13؛ مع تعريف القسم التالي يلزم تمرير نصف القطر.
new Shape[10] تنشئ مصفوفة لا كائناً من Shape. يجوز أن يكون نوع المصفوفة مجرداً، ويُمنع أن يكون نوع الكائن المنشأ مجرداً.لماذا يهم: هذه هي الآلية التي تسمح بمعالجة الأشكال كلها في حلقة واحدة: المرجع من نوع الأب، والكائن من نوع الابن، والدالة تُستدعى بـ polymorphism فتصل إلى تنفيذ الابن.
Shape[] s = new Shape[10]; أم s[0] = new Shape();؟كما يُصرَّح المتغيّر Person p; بلا كائن، تُصرَّح الدالة بلا جسم. Shape لا يستطيع حساب مساحة «شكل اعتباطي»، فيُترك الحساب للابن ويُجبر عليه.
abstract method: دالة مُعلنة غير معرَّفة: ترويسة كاملة بأقواس معاملاتها، ثم ; بدل الجسم بين { }، مثل public abstract void draw(int size);.
public abstract class Shape {
final static int BLACK = 0;
private int color;
public Shape() {
color = BLACK;
}
public void setColor(int c) {
this.color = c;
}
public abstract double getArea(); // بلا جسم
}
setColor تبقى ذات جسم لأن سلوكها واحد لكل الأشكال، وgetArea وحدها تُترك مفتوحة.
public class Circle extends Shape {
final static double PI = 3.14159;
private int radius;
public Circle(int r) {
radius = r;
}
public double getArea() {
return radius * radius * PI;
}
}
Circle ملموس لأنه كتب الدالة المجردة الوحيدة الموروثة بالتوقيع ونوع الإرجاع نفسيهما.
(radius^2)*PI لا يحسب أُسّاً؛ ^ في Java هو XOR. الصواب radius * radius * PI. والشريحة تكتب PI = 3.1419 وهو خطأ مطبعي؛ الصحيح 3.14159. كذلك تختلف colour وcolor بين شريحتين والمقصود واحد.لماذا يهم: الدالة المجردة في الأب عقد: لا شكل ملموس بلا مساحة. وهي أيضاً شرط قانونية الاستدعاء عبر مرجع الأب، كما سيأتي في القسم 4.6.
Circle الدالة getArea؟Circle صنفاً مجرداً، وإلا فشل الترجمة. القاعدة: من يترك دالة مجردة واحدة لا يبقى ملموساً.الشريحة 13 تعرض GeometricObject مجرداً، وCircle وRectangle ابنين يحدّد كلٌّ منهما المساحة والمحيط بطريقته.
GeometricObject
----------------------------------------
- color : int
- filled : boolean
- dateCreated : Date
----------------------------------------
# GeometricObject()
# GeometricObject(color, filled)
+ getColor() : int
+ setColor(color) : void
+ isFilled() : boolean
+ setFilled(filled) : void
+ getDateCreated() : Date
+ toString() : String
+ getArea() : double (abstract)
+ getPerimeter() : double (abstract)
الخط المائل في اسم الصنف أو الدالة يعني abstract. الحقول خاصة، والبنّاءان محميّان protected، والدالتان المائلتان مجردتان، وبقية الدوال ملموسة. المخطط لا يرسم دوال الأب داخل صندوق الابن اختصاراً، لكن Circle وRectangle ملزمان بتنفيذ getArea وgetPerimeter. وCircle يضيف getDiameter() وهي خاصة بالابن لا يعرفها مرجع GeometricObject.
| الرمز في UML | المعنى | مثال في المخطط |
|---|---|---|
| الاسم بالمائل | abstract | GeometricObject، getArea() |
- | private | color، filled |
# | protected | بنّاءا GeometricObject |
+ | public | getColor()، toString() |
Student. والابن قد يكون مجرداً وأبوه ملموساً: Object ملموس وShape قد يكون مجرداً.new.لماذا يهم: السؤال الخادع يقلب الاتجاه. احفظ الجملة الواحدة: «دالة مجردة ⇒ صنف مجرد» صحيحة، و«صنف مجرد ⇒ فيه دالة مجردة» خاطئة.
abstract class بلا دوال مجردة؟new مع الإبقاء على تنفيذ مشترك وحقول مشتركة، وهو حال Student.المرجع من نوع الأب لا يرى إلا ما عُرّف في الأب أو فوقه. الدالة المكتوبة في الابن وحده غير مرئية عبر مرجع الأب، حتى لو كان الكائن الفعلي من ذلك الابن. السبب بجملة الشريحة: الصنف يعرف صنفه الأب لا أصناف أبنائه، والمترجم ينظر إلى نوع المرجع.
class Shape { }
class Star extends Shape {
public void draw() { }
}
class Demo {
public static void main(String[] args) {
Shape someShape = new Star(); // قانوني: Star هو Shape
someShape.draw(); // خطأ ترجمة: Shape لا يعرف draw
}
}
المرجع Shape يفحص أعضاء Shape فقط، ولا يبحث في Star.
الحل المباشر: التحويل قبل الاستدعاء، ((Star) someShape).draw();. يُقبل عند الترجمة لأن Star ابن لـ Shape، لكنه قد يرمي ClassCastException وقت التشغيل إن لم يكن الكائن Star فعلاً.
الحل المعتمد (الشريحة 17): رفع draw إلى Shape دالة مجردة، فيصير الاستدعاء قانونياً بلا تحويل، ويصير new Shape() ممنوعاً.
abstract class Shape {
public abstract void draw();
}
class Star extends Shape {
public void draw() { }
}
class Crescent extends Shape {
public void draw() { }
}
class Demo {
public static void main(String[] args) {
Shape someShape = new Star(); // قانوني
someShape.draw(); // قانوني: كل كائن فعلي ينفّذ draw
Shape bad = new Shape(); // لم يعد قانونياً
}
}
التنفيذ في الأبناء يبقى public. الشريحة حذفت public من Star وCrescent؛ تخفيض الرؤية إلى مستوى الحزمة يفشل الترجمة.
لماذا يهم: هذا هو مبرّر وضع دالة في الأب بلا جسم: ليس لحساب نتيجة هناك، بل ليكون الاستدعاء المتعدد الأشكال قانونياً.
someShape.draw() خطأ في النسخة الأولى وقانوني في الثانية؟ في الأولى draw ليست عضواً في Shape؛ في الثانية صارت مجردة في الأب وكل ابن ملموس يكتبها.Star ليُقبل الاستدعاء عبر مرجع Shape؟الواجهة بناء يشبه الصنف لكنه لا يحمل حالة كائن. غرضه تحديد سلوك مشترك لأصناف مترابطة أو غير مترابطة، وتُترجم إلى ملف bytecode مستقل، وتُستخدم نوعاً كما يُستخدم الصنف المجرد: مرجعاً أو مصفوفة، لا new.
interface: جسمها تصريحات دوال كل تصريح ينتهي بـ ; وبلا تنفيذ، وحقولها ثوابت فقط. في هذا المقرر كل حقل public static final وكل دالة public abstract، ويجوز حذف هذه المعدّلات لأنها مفروضة.
public interface StockWatcher {
final String sunTicker = "SUNW";
final String oracleTicker = "ORCL";
final String ciscoTicker = "CSCO";
void valueChanged(String tickerSymbol, double newValue);
}
الثوابت الثلاثة تصريحات حقول مُهيّأة، وvalueChanged تصريح دالة بلا جسم. الصيغة العامة: modifier interface Name { /* constants */ /* signatures */ }.
public interface T {
public static final int K = 1; // الصيغة الكاملة
public abstract void p();
}
interface U { // الصيغة المختصرة المكافئة
int K = 1; // المعدّلات المحذوفة تبقى سارية
void p();
}
لا فرق سلوكياً بين الصيغتين؛ المعدّلات مفروضة حتى لو غابت من النص.
| المكوّن | مسموح؟ | السبب |
|---|---|---|
| ثابت مُهيّأ | نعم | كل حقل public static final |
| حقل كائن غير مُهيّأ | لا | لا حالة كائن في الواجهة |
| بنّاء | لا | لا كائن يُنشأ |
| تصريح دالة بلا جسم | نعم | كل دالة public abstract |
int x; داخل واجهة بلا تهيئة خطأ ترجمة، لأن الحقل ثابت يجب أن يُهيّأ. وحذف public static final لا يغيّر شيئاً؛ المعدّلات سارية حتى وهي غائبة.لماذا يهم: الواجهة عقد لا تنفيذ. من يضع فيها جسماً لدالة أو حقل حالة يخرج عن نموذج هذا الفصل، ومن ينسى تهيئة الثابت لا يُترجم كوده.
Edible e = new Chicken(); قانوني لأن Chicken ينفّذ الواجهة.قاعدة الملف: في ملف واحد لا يُسمح إلا بنوع public واحد، وكل نوع public يوضع في ملف يحمل اسمه. الأمثلة أدناه تُقرأ على هذا الأساس.
الصنف يلتزم الواجهة بكلمة implements، ويكتب كل دوالها بالتوقيع نفسه ونوع الإرجاع نفسه. ويستطيع أن يمتد من صنف واحد وينفّذ واجهة في الوقت ذاته. في الترميز، «interface» يسم الواجهة، والخط المتقطع مع المثلث الفارغ يعني علاقة تنفيذ، بينما الخط المتصل مع المثلث يعني وراثة صنف.
public class StockApplet extends Applet implements StockWatcher {
public void valueChanged(String tickerSymbol, double newValue) {
// التنفيذ الكامل الذي يجعل الصنف concrete
}
}
صنف واحد يرث Applet وينفّذ StockWatcher؛ الترتيب ثابت: extends ثم implements.
supertype: النوع الأعلى الذي يُشار به إلى عدة أنواع. في مثال الشرائح 25–28: Edible نوع أعلى لـ Chicken وFruit، وAnimal نوع أعلى لـ Chicken وTiger، وFruit نوع أعلى لـ Orange وApple.
ترث Animal وتنفّذ Edible معاً: هي حيوان ويجوز أكلها.
حيوان غير قابل للأكل، فلا ينفّذ Edible ولا يملك howToEat.
ينفّذ Edible ولا يكتب howToEat، لذلك هو abstract.
ابنان ملموسان لـ Fruit، يكتب كل منهما howToEat بطريقته.
public interface Edible {
/** Describe how to eat */
public abstract String howToEat();
}
العقد كله سطر واحد: دالة واحدة تُرجع وصفاً نصياً لطريقة الأكل.
class Animal {
// الحقول والبنّاؤون والدوال محذوفة في الشريحة
}
class Chicken extends Animal implements Edible {
public String howToEat() {
return "Chicken: Fry it";
}
}
class Tiger extends Animal { }
abstract class Fruit implements Edible {
// بلا howToEat، لذا abstract
}
الحقول والبنّاؤون في Animal محذوفة اختصاراً في الشريحة، ولا تؤثر في المثال.
class Apple extends Fruit {
public String howToEat() {
return "Apple: Make apple cider";
}
}
class Orange extends Fruit {
public String howToEat() {
return "Orange: Make orange juice";
}
}
وراثة الشيفرة من Fruit، وتنفيذ السلوك في كل ثمرة.
public class TestEdible {
public static void main(String[] args) {
Object[] objects = {new Tiger(), new Chicken(), new Apple()};
for (int i = 0; i < objects.length; i++) {
if (objects[i] instanceof Edible) {
System.out.println(((Edible) objects[i]).howToEat());
}
}
}
}
المصفوفة من نوع Object، وObject لا يعرف howToEat، فالتحويل (Edible) لازم بعد نجاح الفحص.
instanceof: معامل يفحص هل الكائن من النوع المذكور: صنفاً أو ابناً أو واجهة، ويُعيد true أو false.
Chicken: Fry it Apple: Make apple cider
Tiger يُتخطى لأنه ليس Edible، وOrange لا يدخل المصفوفة فلا يُطبع. نقاش الشريحة 28 يذكر استدعاء sound لعناصر Animal، لكن الكود المعروض في الشريحة 27 لا يستدعيه، فالمخرج سطران فقط.
TestEdible سطران لا ثلاثة. ولماذا Fruit مجرد رغم أن Edible واجهة؟ لأنه ينفّذ الواجهة ولا يكتب howToEat؛ من لا يكمل دوال الواجهة يبقى مجرداً.لماذا يهم: الواجهة هنا صفة لا شجرة نسب. الدجاجة ليست فاكهة، وكلتاهما تُؤكل، فجمعتهما واجهة واحدة بلا وراثة بينهما.
new Orange() إلى المصفوفة، ما المخرج؟Chicken: Fry it ثم Apple: Make apple cider ثم Orange: Make orange juice، لأن Orange ينفّذ Edible فيمرّ من الفحص.الاثنان لا يُنشآن بـ new ويُستخدمان نوعين. الفرق في ما يُسمح بوضعه داخل كل منهما، وفي عدد الآباء.
| الوجه | abstract class | interface |
|---|---|---|
| المتغيرات | بلا قيد: حقول كائن أو ثوابت | كلها public static final |
| البواني | تُستدعى من الأبناء بـ constructor chaining | لا بنّاؤون، وبلا new |
| الدوال | بلا قيد: ملموسة أو مجردة | كلها public abstract وبلا جسم هنا |
| الوراثة | extends لصنف واحد | عدة واجهات بـ implements |
public class NewClass extends BaseClass
implements Interface1, Interface2, InterfaceN {
}
public interface NewInterface
extends Interface1, Interface2, InterfaceN {
// ثوابت ودوال مجردة إضافية
}
الصنف يمتد من صنف واحد وينفّذ أي عدد من الواجهات؛ أما الواجهة فتمتد من عدة واجهات.
subinterface: واجهة تمتد من واجهات أخرى فترث ثوابتها وتواقيع دوالها، وتضيف عليها. NewInterface في المقطع أعلاه مثالها.
مخطط الشريحة 31، والمثلث يشير إلى النوع الأعلى:
Object
↑
Class1 ──▷ Interface1 ──▷ Interface1_1
↑ └────────▷ Interface1_2
Class2 ──▷ Interface2_1
└──▷ Interface2_2
Class1 يمتد من Object، وينفّذ Interface1.Interface1 تمتد من Interface1_1 ومن Interface1_2.Class2 يمتد من Class1، وينفّذ Interface2_1 وInterface2_2 مباشرة، ولا تظهر واجهة باسم Interface2.extends، وأي عدد من الواجهات بـ implements. الواجهة وحدها هي التي تمتد من عدة واجهات. وكل الأصناف تلتقي في جذر واحد هو Object، أما الواجهات فبلا جذر واحد.لماذا يهم: أسئلة instanceof على هذا المخطط تُحل بتتبّع الأسهم صعوداً: وراثة الأصناف على الخط المتصل، والواجهات على المتقطع، ثم واجهات الآباء.
c كائناً من Class2، فعلى أي الأنواع يصدق instanceof؟Object وClass1 وInterface1 وInterface1_1 وInterface1_2 وInterface2_1 وInterface2_2. ولا يصدق على واجهة لم تُنفَّذ في سلسلته.الاثنان يصوغان المشترك، والاختيار يتبع قوة العلاقة لا ذوق الكتابة.
| العلاقة | الأداة | المثال |
|---|---|---|
| is-a قوية: أب وابن واضحان | وراثة أصناف extends | Staff extends Person |
| is-kind-of ضعيفة: الكائن يملك صفة | واجهة implements | Fruit implements Edible |
| الحاجة إلى أكثر من أب | أب واحد + واجهات | Chicken يرث Animal وينفّذ Edible |
تشارك الشيفرة: حقولاً ودوالاً بين كائنات أصناف مترابطة.
تشارك السلوك: ترويسات الدوال فقط بين كائنات أصناف مختلفة.
واجهة للسلوك المشترك، ووراثة للشيفرة المشتركة.
لماذا يهم: لو جعلنا Edible صنفاً أباً لـ Fruit وChicken لاستهلك الاثنان مقعد extends الوحيد، فحُرمت الدجاجة من وراثة Animal. الواجهة لا تستهلك هذا المقعد.
Staff وPerson واجهة أم وراثة؟ وFruit وEdible؟implements لأن is-kind-of صفة لا نسب.| المصطلح | المعنى بالعربية | ملاحظة / مثال |
|---|---|---|
abstract class | صنف مجرد | لا new؛ قد يحوي تنفيذاً وبنّائين |
concrete class | صنف ملموس | مكتمل ويُنشأ، مثل PhdStudent وCircle |
abstract method | دالة مجردة | تصريح بلا جسم ينتهي بـ ; |
data type | نوع بيانات | المجرد والواجهة يصلحان نوعاً مرجعياً |
constructor chaining | سلسلة البنّائين | بنّاء الابن يستدعي بنّاء الأب المجرد |
interface | واجهة | ثوابت وتواقيع دوال؛ ملف bytecode مستقل |
public static final | ثابت واجهة عام | مفروض حتى لو حُذف من النص |
public abstract | دالة واجهة | مفروضة وبلا جسم في هذا الفصل |
implements | ينفّذ | صنف يلتزم واجهة أو أكثر |
subinterface | واجهة فرعية | interface B extends A, C |
supertype | نوع أعلى | Edible نوع أعلى لـ Chicken وFruit |
instanceof | فحص النوع | صنف أو ابن أو واجهة |
| is-a | علاقة أب وابن قوية | تُنمذج بـ extends بين أصناف |
| is-kind-of | علاقة صفة ضعيفة | تُنمذج بـ implements |
# في UML | protected | بنّاءا GeometricObject |
«interface» | وسم الواجهة في UML | مع خط متقطع ومثلث فارغ |
| # | الخطأ | الصواب |
|---|---|---|
| 1 | new على Student أو Shape أو على واجهة | new على ابن ملموس، والمرجع يجوز أن يكون من النوع المجرد أو من الواجهة |
| 2 | دالة مجردة داخل صنف غير مجرد | إعلان الصنف نفسه abstract |
| 3 | الاعتقاد أن كل صنف مجرد يحوي دالة مجردة | يجوز أن يخلو منها، مثل Student |
| 4 | ابن ملموس يترك getArea أو howToEat بلا تنفيذ | ينفّذها كلها بالتوقيع ونوع الإرجاع نفسيهما، أو يُعلَن مجرداً |
| 5 | حقل كائن أو بنّاء داخل واجهة | ثوابت مُهيّأة فقط، وبلا بنّائين |
| 6 | class C extends A, B | extends لصنف واحد ثم implements لأي عدد من الواجهات |
| 7 | استدعاء draw عبر مرجع Shape والدالة في Star فقط | إعلان draw في Shape؛ الصنف لا يرى دوال أبنائه |
| 8 | نسخ radius^2 بوصفه أُسّاً | radius * radius؛ المعامل ^ هو XOR |
| الشرائح | القسم |
|---|---|
| 1–2 | خريطة الفصل: أصناف مجردة، دوال مجردة، واجهات |
| 3–5 | 1. الدافع: Animal وStudent وأبناؤه، ومنع إنشاء Student |
| 6–8 | 2. تعريف abstract class، ومثال Student / PhdStudent |
| 9 و18 | 3. الصنف المجرد نوع بيانات: Shape[] ومنع new Shape() |
| 10–12 | 4. لماذا getArea مجردة، وصيغة الدالة بلا جسم، وShape / Circle |
| 13–15 | 5. UML لـ GeometricObject وقواعد الإكمال والتجريد |
| 16–17 | 6. ما يراه نوع المرجع: draw عبر مرجع الأب قبل التجريد وبعده |
| 19–23 | 7. تعريف الواجهة، وجسم StockWatcher، وتكافؤ المعدّلات |
| 24–28 | 8. StockApplet، ومخطط Edible / Animal، وTestEdible |
| 29–31 | 9. جدول المقارنة، وتعدد الواجهات، وsubinterface، ومخطط Class2 |
| 32–33 | 10. is-a القوية وis-kind-of، ومشاركة الشيفرة مقابل مشاركة السلوك |
Student وShape مجردين: تنفيذ مشترك، ونوع واحد لـ polymorphism، وإجبار الأبناء على سلوك لا يُكتب في الأب.new على النوع المجرد غير قانونية، وبنّاء المجرد يُستدعى من الابن.someShape.draw() قبل رفع draw وبعده، والجملة المفتاحية: الصنف يعرف أباه لا أبناءه.;، والحقل ثابت مُهيّأ.TestEdible سطران، وFruit مجرد لأنه لا يكتب howToEat، وinstanceof Edible يصدق على Chicken وApple.extends الواحد مقابل implements المتعدد، وتعريف subinterface.c من Class2، وقاعدة is-a القوية مقابل is-kind-of: شيفرة مشتركة بالوراثة وسلوك مشترك بالواجهة.Cheat Sheet · 4 فصول
Java OOP — Quick Reference
جداول مركزة لا شرح جديد: معاني المعدّلات وأخطاؤها، حدود الوصول، خمس مقارنات تُسأل حرفياً، اثنتا عشرة قاعدة، وبطاقتان للأخطاء وتسلسل البناء.
| الكلمة | المعنى | متى تُستخدم | خطأ شائع |
|---|---|---|---|
static |
يربط العضو بالصنف لا بالمثيل: نسخة واحدة يتشاركها كل الكائنات | عدّاد كائنات، ثابت مشترك static final، دوال أدوات كـ Math.pow |
قراءة حقل مثيلي أو نداء دالة مثيلية من main بلا كائن |
final |
تجميد: متغير ثابت لا يُعاد إسناده، أو صنف لا يُمدّد، أو دالة لا تُعاد تعريفها | ثوابت (static final int MAX = 100;)، أصناف مثل Math، منع التخصيص |
خلط الوجوه الثلاثة: ظن أن final على صنف يمنع إنشاء كائناته |
this |
مرجع الكائن الذي استُدعيت عليه الدالة المثيلية | كشف حقل محجوب (this.radius = radius)، باني آخر this(...) |
استخدامه في سياق static، أو كتابة radius = radius فيبقى الحقل 0.0 |
super |
مرجع الأب: بانيه، أو نسخته من دالة أُعيد تعريفها، أو حقوله العامة والمحمية (protected) |
أول جملة في باني الابن، أو super.toString() لضم نص الأب |
وضعه بعد جملة، أو الجمع بين this() وsuper()، أو توقع أن البواني تُورَّث |
extends |
يعلن وراثة صنف من أب واحد | علاقة is-a قوية ومشاركة شيفرة: Staff extends Person |
class C extends A, B: وراثة الأصناف المتعددة غير مدعومة |
implements |
يلتزم واجهة أو أكثر ويكتب كل دوالها بالتوقيع نفسه | صفة is-kind-of مثل Chicken implements Edible، أو أب واحد + عدة واجهات |
ترك دالة واجهة بلا تنفيذ في صنف غير مجرد، أو تخفيض رؤيتها عن public |
abstract |
على الصنف: لا new. على الدالة: تصريح بلا جسم ينتهي بـ ; |
أب عام (Student، GeometricObject)، وإجبار الأبناء على getArea وdraw |
دالة مجردة في صنف غير مجرد، أو ظن أن كل صنف مجرد يحوي دالة مجردة |
interface |
عقد سلوك: ثوابت public static final وتواقيع public abstract، بلا حالة كائن |
سلوك مشترك بين أصناف قد لا تربطها قرابة (Edible)، ونوع لمرجع أو مصفوفة |
حقل كائن أو باني داخل الواجهة، أو new على الواجهة |
@Override |
تعليق يعلن نية إعادة تعريف دالة موروثة، ويتحقق منها المترجم | فوق كل overriding، ومنها toString |
وضعه على دالة private أو static، أو على p(int) التي تُحمّل p(double) فتفشل الترجمة |
private |
العضو مرئي داخل الصنف وحده، ومنه كود الصنف على كائن آخر | تغليف الحقول خلف getter/setter، باني يمنع الإنشاء، دوال مساعدة |
ظن أنه يمنع c.x داخل الصنف نفسه، أو قراءته من صنف آخر |
protected |
مرئي للحزمة، وللابن في حزمة أخرى عبر مرجع من نوعه هو (this) لا من نوع الأب |
أعضاء مخصصة للتخصيص بين أب وابن (الرمز # في UML) |
إضعافه إلى private عند إعادة التعريف؛ التقوية مسموحة والإضعاف ممنوع |
public |
مرئي من أي مكان، داخل الحزمة وخارجها | واجهة الصنف: بوانيه وجالباته ودوال الواجهة التي ينفّذها | الاعتقاد أن العضو أو الصنف بلا معدّل هو public |
default |
بلا كلمة معدّل: رؤية الحزمة فقط، لا يعبرها ابن ولا صنف غريب | عندما يكفي التعاون داخل الحزمة نفسها | كتابته صراحةً ظنّاً أنها public، أو توقع جسم افتراضي لدوال الواجهة |
public abstract وكل حقولها public static final مهيّأة — حذف المعدّلات من النص لا يلغيها.| المعدّل | نفس الصنف | الحزمة نفسها | الوريث (ولو في حزمة أخرى) | أي مكان |
|---|---|---|---|---|
private | نعم | لا | لا | لا |
default (بلا كلمة) | نعم | نعم | لا | لا |
protected | نعم | نعم | نعم | لا |
public | نعم | نعم | نعم | نعم |
C4 extends C1 في حزمة أخرى يرى public وprotected فقط؛ وC2 في الحزمة نفسها يرى default ولا يرى private.| العامل | Overloading | Overriding |
|---|---|---|
| التوقيع | الاسم نفسه وقائمة وسطاء مختلفة (عدداً أو نوعاً) | الاسم نفسه وأنواع الوسطاء نفسها: التوقيع مطابق |
| مكان التعريف | في الصنف نفسه، أو في الابن بتوقيع مختلف (p(int) أمام p(double)) | في الابن، لدالة موروثة مرئية وغير ساكنة |
| وقت القرار | وقت الترجمة، حسب أنواع الوسطاء | وقت التشغيل (dynamic binding)، حسب نوع الكائن الفعلي |
| الوراثة | لا تشترط وراثة، ونسخة الأب تبقى موجودة | تشترط وراثة، ويُستبدل جسم الأب ويُنادى بـ super.m() |
| نوع الإرجاع | قد يختلف مع اختلاف الوسطاء، وتغييره وحده لا يصنع تحميلاً | يبقى متوافقاً مع نوع إرجاع الأب، وهو خارج التوقيع |
| الوسم | لا @Override | @Override مستحسن، ويفشل الترجمة إن لم توجد دالة مطابقة |
| المثال والمخرج | p(10) ← 10 وp(10.0) ← 20.0 | p(10) ← 10.0 وp(10.0) ← 10.0 |
p(int) في الابن لا تُلغي p(double) في الأب؛ والـstatic تُخفى لا تُستبدل، وprivate لا تُعاد تعريفاً.| العامل | abstract class | interface |
|---|---|---|
| الغرض | مشاركة شيفرة وحالة، ومنع new على النوع العام | عقد سلوك مشترك بين أصناف قد لا تربطها قرابة |
| الحالة | حقول كائن أو ثوابت، بلا قيد | كل الحقول public static final ومهيّأة |
| البواني | له بوانٍ تُستدعى من الابن بـsuper() ضمن سلسلة البناء | لا بوانٍ، ولا new عليه إطلاقاً |
| الوراثة المتعددة | extends لصنف واحد فقط | الصنف ينفّذ عدة واجهات، والواجهة الفرعية subinterface تمتد من عدة واجهات |
| الدوال | ملموسة أو مجردة، بأي رؤية | public abstract بلا جسم في هذا المقرر |
| العلاقة | is-a قوية: Staff extends Person | is-kind-of ضعيفة: Fruit implements Edible |
| متى تختار | حين تشترك الأصناف في حقول وشيفرة وشجرة نسب | حين تشترك في فعل فقط، أو حين يلزم أكثر من «مقعد» سلوكي |
new؛ ولا شجرة واحدة للواجهات، بينما كل الأصناف تلتقي في Object.| العامل | static | instance |
|---|---|---|
| عدد النسخ | نسخة واحدة لكل الصنف | نسخة لكل كائن |
| الوصول | باسم الصنف بلا كائن Circle.getNumberOfObjects()، أو عبر مرجع، والأفضل باسم الصنف | عبر مرجع كائن بعد new |
| يصل إلى المثيلي | لا مباشرةً، إلا عبر مرجع صريح a.i وa.m1() | نعم، إليه وإلى الثابت |
| this | لا وجود له: لا كائن مستقبِل | يشير إلى الكائن المستقبِل |
| UML | مسطَّر | بلا تسطير |
| الأثر | تغييره يظهر عند كل الكائنات (عداد من 0 إلى 2) | تغييره لا يمس كائناً آخر (c1 = 9 وc2 = 5) |
| أمثلة | numberOfObjects، Math.pow، MAX_RADIUS | radius، getArea() |
| العامل | == | equals() |
|---|---|---|
| ماذا يقارن | قيمة النوع الأولي، وعنوان المرجع للكائن | ما تعرّفه إعادة التعريف من معنى للمساواة |
| السلوك الافتراضي | مقارنة هوية: هل هما الكائن نفسه | نسخة Object تقارن العناوين أيضاً، فلا فرق بلا إعادة تعريف |
| متى تستخدم | نفس الكائن؟ أو فحص null، وقيم الأعداد | تطابق الحروف في String أو القيمة في الغلاف |
| خطأ شائع | مقارنة نصين بـ== فيفشل رغم تطابق الحروف | افتراض أنها تقارن المحتوى دائماً بلا إعادة تعريف |
| الأثر | مرجعان إلى الكائن نفسه يعطيان true | كائنان مختلفان بقيم متساوية يعطيان true بعد التعريف |
| الاتجاه | الصيغة والقسر | ما يظهر والخطر |
|---|---|---|
توسيع upcasting | Animal a = new Cat(); — إسناد مباشر بلا قسر | الدوال المعاد تعريفها تُنفَّذ بنسخة الكائن الفعلي |
تضييق downcasting | Student s = (Student) per; — القسر واجب | تظهر دوال الابن، وClassCastException إن لم يكن الكائن من النوع |
| مرجع الأب | يرى أعضاء الأب وأسلافه فقط | someShape.draw() خطأ ترجمة إن كانت draw في Star وحدها |
| الفحص قبل القسر | if (o instanceof Edible) ((Edible) o).howToEat(); | الفحص يقبل الصنف وابنَه والواجهة المنفَّذة |
new؛ والافتراضي يُدرج فقط إن لم يُكتب أي باني.package-private لا public.private حدّه الصنف لا الكائن؛ والفرع خارج الحزمة يرى public وprotected فقط.static ملك الصنف ونسخته واحدة؛ لا يصل إلى المثيلي مباشرةً، وthis غير موجود فيه.super(...) أول جملة، وthis() وsuper() لا يجتمعان، والبناء يصعد إلى الجذر قبل جسم الابن.extends، والواجهات متعددة بـimplements.static تُخفى لا تُستبدل، والدالة private لا تُعاد تعريفاً، والوصول يُقوّى ولا يُضعف.abstract، وكل ابن ملموس ينفّذ ما ورثه مجرداً أو يُعلَن مجرداً.ClassCastException.void للباني أو توقع قيمة إرجاع منه.default constructor بعد كتابة Circle(double).main بلا كائن، أو استخدام this في static.c1.numberOfObjects نسخة مستقلة عن c2.private من صنف آخر، أو نداء دالة default من حزمة أخرى.setter يسند أي قيمة، فيُلغى نفع التغليف.Circle() فارغاً والأب بلا باني بلا وسطاء، أو كتابة جملة قبل super(...).p(int) كإعادة تعريف لـp(double).@Override على دالة private أو static، أو إضعاف protected إلى private.I am Wolf, I can move مع أن move لم تُعد تعريفها؛ الناتج نسخة الأب.new على صنف مجرد أو واجهة، أو قسر بلا instanceof.class Person {
Person() { System.out.println("(1) Person"); }
}
class Employee extends Person {
Employee() {
this("(2) Employee overloaded");
System.out.println("(3) Employee no-arg");
}
Employee(String s) { System.out.println(s); }
}
class Faculty extends Employee {
Faculty() { System.out.println("(4) Faculty"); }
}
النداء: new Faculty().
main ثم new Faculty().Faculty() وفي أوله super() نحو Employee().Employee() يبدأ بـthis(String) لا بـsuper.Employee(String) ينادي Person() ضمناً.لا يعمل جسم الباني قبل اكتمال بناء الأب، وthis(...) يؤخّر نداء الأب إلى الباني الآخر.
Mock Exam · 40 questions · 45 min
Practice Quiz — OOP in Java
40 سؤالاً موزّعاً على الفصول الأربعة: 24 اختيار من متعدد، و10 صح/خطأ، و6 «ما ناتج الكود؟». التصحيح فوري، وكل سؤال مرفق بتفسير يشرح الصواب وخطأ كل مشتّت.
radius من النوع double، أما القيمة فحالة تخص الكائن. (a) يخلط القالب بالقيمة. (b) الباني لا يخزّن شيئاً في نفسه، بل يمرّر القيمة إلى حقل الكائن عند new. (c) صنف main يملك متغيرات مرجع إلى الكائنات لا حالتها. الدليل العملي: circle2.setRadius(100) لا يغيّر circle1.private ودوال public. (b) عكس الدورين. (c) خطأ: تغليف جيد قد يقترن بتجريد ضعيف، والعكس صحيح. (d) خطأ: الكلمة المفتاحية abstract أداة تقنية لتحقيق التجريد لا تعريفه، والتغليف لا علاقة له بالوراثة.Circle الذي كتب بانيَين Circle() وCircle(double) لا يملك بانياً ثالثاً ضمنياً. (a) خطأ: وجود أي باني يلغي الإضافة. (c) و(d) شرطان مخترعان: الصنف المجرد قد يملك بواني تُستدعى من الأبناء، والحقول الخاصة لا علاقة لها بالأمر.z معرّف بلا أي معدّل وصول داخل صنف عام C1 في الحزمة p1. أي كود يستطيع قراءة o.z؟public على الصنف لا ينقل أعضاءه إلى public. (b) خطأ: هذا سلوك protected، والابن في حزمة أخرى لا يرى الافتراضي. (d) خطأ لأنه يضيّق أكثر من اللازم؛ الأصناف الشقيقة في p1 تراه.makeNoise() عبر مرجع Animal يُنفَّذ صوت Dog أو Cat حسب نوع الكائن الفعلي، وهذا هو تعدد الأشكال.Animal العام، وهو ما لا يحدث.Circle.java الذي لا يحوي main يمكن تشغيله مباشرة بالأمر java Circle.Circle تعريف صنف لا برنامج: يُترجَم إلى Circle.class بنجاح، لكن التشغيل يتطلب صنفاً يحوي main (main class) مثل TestCircle، فيُطلق الأمر java TestCircle وتفشل محاولة تشغيل Circle برسالة عدم وجود دالة main.public class Circle {
private double radius;
Circle() { radius = 1; }
Circle(double r) { radius = r; }
double getRadius() { return radius; }
void setRadius(double r) { radius = r; }
}
class Test {
public static void main(String[] args) {
Circle c1 = new Circle();
Circle c2 = new Circle(25);
c2.setRadius(100);
System.out.println(c1.getRadius() + " " + c2.getRadius());
}
}
c1 بُني بالباني بلا وسطاء فنصف قطره 1.0، وc2 بُني بـ25 ثم غيّره setRadius(100) إلى 100.0. (a) يعامل radius كأنه static وهو حقلاً مثيلياً. (b) خطأ: setRadius دالة عادية تعدّل حقل الكائن متى نُوديت. (c) خطأ: الباني ذو الوسيط نُفِّذ فعلاً عند new Circle(25). الكائنان مستقلان، وتغيير أحدهما لا يمسّ الآخر.double radius; وstatic int numberOfObjects = 0;، وأنشأنا منه كائنين. أي وصف صحيح لدور الحقل الثابت؟static تربط العضو بالصنف لا بالمثيل: موضع واحد يشير إليه كل الكائنات، فزيادة العداد من أي باني تظهر عند الجميع. والقراءة باسم الصنف أوضح لأن القيمة ليست ملك كائن. (a) خطأ: هذا وصف الحقل المثيلي، وc1.numberOfObjects وc2.numberOfObjects قراءتان للخانة الواحدة نفسها، لا خانتان. (c) خطأ: الحقل الثابت يُهيَّأ مع تحميل الصنف قبل أي كائن، ومكانه مع بيانات الصنف لا داخل الكائن. (d) خطأ: خلط بين static وfinal؛ الثبات من الأخيرة.int j = i; داخل main والصنف فيه int i = 5; مثيلي، بينما ينجح داخل دالة مثيلية؟this)، أما الثابتة فلا كائن مستقبِل لها، فلا تصل إلى عضو مثيلي إلا عبر مرجع صريح مثل a.i أو a.m1() بعد new A(). (b) خطأ لأن main تقرأ الثوابت مثل k وتستدعي m2 بلا مشكلة. (c) خطأ: final لا علاقة لها بسياق الاستدعاء. (d) خطأ: المشكلة ليست ترتيب التهيئة بل غياب الكائن الذي يحمل الحقل.C فيه حقل private boolean x. أي استخدام قانوني له؟private حدّه الصنف لا الكائن: كود داخل C يقرأ x حتى عبر c2.x لكائن آخر من النوع نفسه، بينما صف Test خارج C يفشل ترجمته. (a) خطأ: المنع على الخارج فقط. (b) خطأ: الحزمة لا تتجاوز الخاص. (d) خطأ: الخاص لا يورَّث ظهوراً؛ الابن يصل إلى الحالة عبر دوال عامة مثل setX.setRadius فيه radius = (r >= 0) ? r : 0;، ثم نُودي setRadius(-3). ما النتيجة وما قيمتها التصميمية؟0، فيبقى نصف القطر في نطاق صحيح. القيمة التصميمية أن الفحص في مكان واحد داخل setter: لو كان الحقل public لكتب أي عميل myCircle.radius = -3 وألغى الحماية. (a) خطأ: الإسناد يحدث بالقيمة 0 لا يُرفض. (b) يصف السلوك قبل التغليف. (c) خطأ: double يقبل السالب، والاستثناء لم يُكتب في الشفرة.circle1.radius = 1 وcircle2.radius = 2. الدالة swap1(x, y) تبدّل الوسيطين المرجعيين محلياً، والدالة swap2(x, y) تبدّل x.radius وy.radius. ماذا يُطبع بعد كل نداء بالترتيب؟swap1 يبدّل نسختين محليتين فيعود كل شيء كما كان، وswap2 يعدّل حقول الكائنين نفسهما فيظهر التبديل. (b) يتوقع أن تبديل المراجع يعود إلى main. (c) يتجاهل أن تعديل الحقول يعود. (d) خطأ: الكائنات تُمرَّر بمراجعها، والتبديل المحلي مشروع.Circle(double radius) مكتوب فيه radius = radius; بلا this. ما قيمة الحقل بعد new Circle(5)؟radius يعني الوسيط، وradius = radius إسناد للذات لا يغيّر الحقل. الحقل بلا قيمة ابتدائية في الإعلان فيبقى 0.0، والتصحيح this.radius = radius. (a) يفترض حجباً معاكساً. (c) خطأ: الحجب مسموح في Java والترجمة تنجح. (d) خطأ: 1.0 تكون للحقل لو أُعلن بقيمة ابتدائية أو ضبطه الباني الافتراضي.static موضع واحد في الذاكرة، فتعديله من كائن واحد يظهر عند كل كائنات الصنف.numberOfObjects في مثال الدوائر خانة واحدة: بعد بناء كائنين يُقرأ 2 من أي مرجع ومن اسم الصنف، بينما radius المثيلي يبقى مستقلاً لكل كائن. والخطأ الشائع هو افتراض نسخة لكل كائن، كما لو أن c1.numberOfObjects وc2.numberOfObjects خانتان مختلفتان.swap2. وما لا يعود هو إعادة إسناد المعامل نفسه (x = y)، أو تعديل متغير محلي مثل times؛ وهذا بالضبط سبب فشل swap1 وحده.public class Counter {
double radius;
static int count = 0;
Counter() { radius = 1; count++; }
Counter(double r) { radius = r; count++; }
}
class Test {
public static void main(String[] args) {
Counter c1 = new Counter();
Counter c2 = new Counter(5);
c1.radius = 9;
System.out.println(c1.radius + " " + c2.radius + " " + Counter.count);
}
}
c1.radius صار 9.0 بعد الإسناد المباشر (والحقل بلا معدّل فهو مرئي داخل الحزمة نفسها)، وc2.radius بقي 5.0، والعداد static زاد في البانيَين فصار 2. (a) يعامل radius كأنه مشترك. (b) خطأ: البانيان يزيدان العداد نفسه. (d) يتجاهل إسناد c1.radius = 9.Shape فيه باني واحد Shape(String, boolean) فقط. أي باني ابن يُترجم بنجاح؟super() الضمني. (a) و(d) يعتمدان على باني أب بلا وسطاء غير موجود، فيفشل الترجمة. (c) خطأ لأن super(...) يجب أن يكون أول جملة في الباني، فلا يجوز أن تسبقه أي جملة ولو كانت إسناد حقل.B فيه void p(double i) { print(i * 2); }، والابن A فيه void p(int i) { print(i); }. ثم A x = new A(); x.p(10); x.p(10.0);p(int) توقيع مختلف، فهو overloading لا إعادة تعريف، ونسخة الأب p(double) تبقى. الوسيط 10 يطابق p(int) تماماً فيُطبع 10، والوسيط 10.0 لا يطابق إلا p(double) فيُطبع 20.0. (b) هو ناتج حالة إعادة التعريف p(double) في الابن. (c) يفترض أن كلا الاستدعاءين يذهب للأب. (d) يعكس ترتيب الحسم.P فيه static void m() تطبع "P"، وC extends P تعرّف static void m() تطبع "C". ثم P ref = new C(); ref.m();P فيُطبع P. (a) يطبّق قاعدة إعادة التعريف والربط الديناميكي على حالة ساكنة. (b) الشفرة تُترجم بلا خطأ؛ الإخفاء سلوك مشروع. (c) خطأ: إسناد كائن ابن إلى مرجع أب توسيع بلا قسر.C1 في الحزمة p1 فيه x عام وy محمي وz بلا معدّل وu خاص. أي أعضاء تراها C4 extends C1 وهي في الحزمة p2؟public وprotected فقط، فيصل إلى x وy. (a) خطأ: private لا يعبر حدّ الصنف حتى إلى الابن. (c) خطأ: protected مصمّم ليصل إلى الأبناء خارج الحزمة. (d) خطأ: z افتراضي، ونطاقه الحزمة التي عُرّف فيها الأب أي p1، لا حزمة الابن.Person p = new Student(); وStudent s2 = (Student) p;؟final صحيحة؟final على المتغير تمنع إعادة الإسناد، وعلى الصنف تمنع التمديد مثل final class Math، وعلى الدالة تمنع إعادة التعريف في الابن. (a) تخلط final بـabstract؛ منع الإنشاء وظيفة الثاني. (b) التحميل الزائد توقيع مختلف ولا يمسّه final. (c) الجمع بينهما تناقض يرفضه المترجم.super(...) يجب أن يكون أول جملة في باني الابن، ولا يجتمع مع this(...) في الباني نفسه.this(...) انتقل إلى باني آخر في الصنف نفسه، فيؤجَّل نداء الأب إلى ذلك الباني، ولا يصح الجمع بين النداءين. ومتى غاب الاثنان وُضع super() ضمنياً بشرط وجود باني أب بلا وسطاء.Shape(String, boolean) كأنها دالته.super(...) صريحاً أو ضمنياً، ولهذا يعيد الابن كتابة بوانيه بدل استعارتها.public void print() يستطيع تضييق وصولها إلى private.protected في الأب تصير public في الابن، أما جعلها private فيفشل الترجمة، لأن كل من يستدعي الدالة عبر مرجع الأب سيفقد الوصول إليها. وإذا كانت دالة الأب private أصلاً فهي غير مرئية، وما يكتبه الابن بالاسم نفسه دالة جديدة لا إعادة تعريف.extends أبوه java.lang.Object، ولهذا تُورَّث toString() ولو لم تُكتب.Object، فالتنفيذ الافتراضي لـtoString متاح دائماً ويُرجع اسم الصنف ثم @ ثم عدداً يمثل الكائن مثل Loan@15037e5، وprintln(loan) تستدعيه ضمنياً. ولهذا يعيد الصنفان Shape وCircle تعريفه ليعطيا نصاً مفهوماً.class Person {
Person() { System.out.println("(1)"); }
}
class Employee extends Person {
Employee() {
this("(2)");
System.out.println("(3)");
}
Employee(String s) { System.out.println(s); }
}
public class Faculty extends Employee {
Faculty() { System.out.println("(4)"); }
public static void main(String[] args) { new Faculty(); }
}
new Faculty() يدخل Faculty() فيستدعي super() نحو Employee()، الذي يبدأ بـthis("(2)") فينتقل إلى Employee(String)، وهذا يستدعي Person() ضمنياً فيُطبع (1) أولاً، ثم يعود فيُطبع الوسيط (2)، ثم (3) من باني Employee()، ثم (4). (a) تظن أن الوسيط يُطبع قبل بناء الأب. (b) تعكس ترتيب this. (c) تبدأ بالابن، وجسم الابن لا يعمل قبل اكتمال الأب.class Animal {
void eat() { System.out.println("Animal can eat"); }
void move() { System.out.println("Animal can move"); }
}
class Cat extends Animal {
void eat() { System.out.println("I am Cat, I can eat"); }
void move() { System.out.println("I am Cat, I can move"); }
}
class Wolf extends Animal {
void eat() { System.out.println("I am Wolf, I can eat"); }
}
class Test {
public static void main(String[] args) {
Animal[] a = { new Cat(), new Wolf() };
a[0].eat();
a[1].move();
}
}
Animal والكائنات من أبناء، فالاختيار وقت التشغيل لنوع الكائن. Cat أعادت تعريف eat فتُطبع نسختها، وWolf لم تعد تعريف move فتبقى نسخة الأب. (a) تتوقع تعريفاً غير موجود في Wolf. (b) تتجاهل إعادة التعريف في Cat. (d) خطأ: الإسناد قانوني، وليس مطلوباً من الابن تعريف كل دوال الأب، بل الدوال المجردة وحدها.abstract class Student بلا أي دالة مجردة، وما الفائدة؟Student مجرد ليمنع إنشاء «طالب عام» ويُبقي setLogin وgetLogin مشتركة بين PhdStudent وغيره. (a) تقلب الاتجاه. (c) خطأ: الواجهة بلا بنّاء وبلا حقول كائن ولا تنفيذ، والصنف المجرد يحمل حالة وتنفيذاً وهو أب واحد. (d) شرط مخترع لا أساس له.Shape صنف مجرد، وgeo من نوع Shape[]. أي العبارتين قانونية: Shape[] geo = new Shape[10]; أم geo[0] = new Shape();؟new على النوع المجرد لا استخدامه نوعاً. إنشاء المصفوفة يُهيّئ عشر خانات null بلا أي كائن، ويُملأ كل عنصر بابن ملموس مثل new Circle(5). (a) خطأ: المصفوفة لا تُنشئ كائنات. (b) معكوسة. (c) خطأ: النوع المجرد والواجهة نوعان مرجعيان صالحان تماماً، وهذا أساس polymorphism.public interface T كُتب int K = 1; بلا معدّلات. ما وضع هذا الحقل؟public static final حتى لو حُذفت المعدّلات، وكل دالة public abstract. ولأن الحقل ثابت فلا بد أن يُهيَّأ في إعلانه. (a) خطأ: الواجهة لا تحمل حالة كائن. (b) خطأ: لا وجود لوصول افتراضي داخلها، والوضع المفروض هو العام. (d) خطأ: الثبات مفروض، والتعديل ممنوع.class Star extends Shape { public void draw() { } } ثم Shape someShape = new Star(); someShape.draw(); — ما الحكم وما العلاج؟Shape فلا يجد draw. العلاج المباشر تحويل قسري قبل الاستدعاء (وقد يرمي ClassCastException إن لم يكن الكائن Star)، والعلاج الأفضل رفع draw إلى Shape مجردة فيصير الاستدعاء قانونياً بلا تحويل. (a) تتجاهل فحص الترجمة. (c) الخطأ ترجمة لا وقت تشغيل. (d) @Override لا تضيف عضواً إلى الأب.Staff وPerson علاقة is-a قوية تشارك حقولاً وشيفرة فتُنمذج بوراثة صنف. Fruit وEdible علاقة is-kind-of ضعيفة تشارك صفة فتُنمذج بواجهة، ولولاها لاستهلكت الدجاجة مقعد extends الوحيد. (b) عكس الدورين. (c) عكس الكلمتين. (d) صحيح أنهما يمنعان new ويصلحان نوعاً، لكنهما يختلفان في المحتوى وعدد الآباء.public static final وتواقيع دوال public abstract، بلا حقول كائن وبلا بنّاء، ولهذا لا new عليه. والصيغة class NewClass extends BaseClass implements Interface1, Interface2 هي الالتفاف على قيد الوراثة الأحادية: extends لصنف واحد، وimplements لعدد غير محدود. أما الواجهة نفسها فتمتد من عدة واجهات وتُسمّى subinterface.interface Edible { String howToEat(); }
class Animal { }
class Chicken extends Animal implements Edible {
public String howToEat() { return "Fry it"; }
}
class Tiger extends Animal { }
abstract class Fruit implements Edible { }
class Apple extends Fruit {
public String howToEat() { return "Make cider"; }
}
public class Test {
public static void main(String[] args) {
Object[] objs = { new Tiger(), new Chicken(), new Apple() };
for (Object o : objs) {
if (o instanceof Edible) System.out.println(((Edible) o).howToEat());
}
}
}
Tiger ليس Edible فيفشل الفحص ويُتخطى بلا استثناء، وChicken وApple ينفّذان howToEat فيُطبع سطراهما بالترتيب. (a) خطأ: الشرط يستبعد النمر. (b) خطأ: Fruit مجرد لأنه ترك دالة الواجهة، وهذا قانوني، والابن الملموس Apple نفّذها. (d) خطأ: التحويل محميّ بـinstanceof، فما لا يُفحص لا يُحوَّل. وObject لا يعرف howToEat، ولهذا يلزم التحويل بعد نجاح الفحص.abstract class Shape {
public abstract double getArea();
}
class Rect extends Shape {
private double w = 2, h = 3;
public double getArea() { return w * h; }
}
public class Test {
public static void main(String[] args) {
Shape s = new Rect();
System.out.println(s.getArea());
}
}
Shape s مرجع من نوع مجرد يقبل كائن ابن ملموس، والاستدعاء يُحسم وقت التشغيل فيُنفَّذ Rect.getArea فيُطبع 6.0. (a) تخلط المساحة بالمحيط. (c) خطأ: النوع المجرد صالح للمراجع والمصفوفات، والممنوع new عليه فقط. (d) خطأ: الدالة المجردة عقد بلا جسم في الأب، والابن الملموس ملزم بتقديم الجسم، ولو تركه لوجب إعلانه مجرداً.public static final مفروض ويُهيَّأ في إعلانه. (c) مشروع: باني protected هو العُرف في الأصناف المجردة لأن الأبناء وحدهم يستدعونه بـsuper.Dog عبر مرجع من نوع الأب؟sound مجردة إلى Animal يجعل الاستدعاء قانونياً عبر المرجع، والربط الديناميكي ينفّذ نسخة Dog. (b) خطأ ترجمة لأن Animal مجرد. (c) إسناد كائن أب إلى مرجع ابن: غير متوافق بلا قسر، ولن يمرّ إلا بقسر يفشل وقت التشغيل. (d) bark ليست في Animal، والتحويل إلى Animal لا يضيف عضواً.Animal a = new Dog(); توسيع بلا قسر، وDog d = (Dog) a; تضييق يلزمه القسر وقد يرمي ClassCastException إن لم يكن الكائن Dog فعلاً.instanceof.Circle وCircleWithStaticMembers وFaculty وTestEdible يدوياً على الورق.super وthis، قواعد التجريد والواجهات، ثم أعد المحاولة.