Java ملخص المراجعة

COMPUTER SCIENCE · JAVA

Object-Oriented Programming

ملخص مراجعة عربي لمقرر البرمجة الكائنية بجافا: أربعة فصول و169 شريحة ومصطلحات بلغتين، مرتّبة لقراءة طويلة دون إجهاد العين.

  • 4 فصول
  • 169 شريحة
  • مصطلحات English + شرح عربي
  • مراجعة قبل الاختبار

خطة المراجعة (285 دقيقة)

285 دقيقة من العمل المركّز، موزّعة على أربع جلسات.

  1. 90 د

    الفصلان 1 و2 — الأساسيات والأصناف

    اقرأ البطاقات والجداول، ثم أعد كتابة مثال الدائرة من الذاكرة.

  2. 90 د

    الفصل 3 — الوراثة وتعدد الأشكال

    ثبّت super وسلسلة البناء وجدول حدود الوصول قبل الانتقال.

  3. 60 د

    الفصل 4 — الأصناف المجردة والواجهات

    قارن abstract class بـinterface في جدول واحد.

  4. 45 د

    الاختبار التجريبي

    أجب دون الرجوع للملخص، ثم اقرأ تفسير كل سؤال أخطأت فيه.

تقدّمك

0%

أنجزت 0 من 6

يُحفظ التقدّم في متصفحك، ويُحدَّث عند تعليم أي فصل بـ«تمت المراجعة».

01

Chapter 1 · 43 slides

مقدمة في البرمجة الكائنية

Introduction to Object-Oriented Programming

من فكرة أن البرنامج كائنات تتفاعل إلى أول صنف يُترجَم ويُشغَّل: المصطلحات، ثم النمذجة والمتطلبات، ثم الأعمدة الأربعة، وينتهي الفصل بصنف Circle وبوانيه ومحددات وصوله.

1.1 البرمجة الكائنية وأهدافها Object-Oriented Programming

الفكرة. البرمجة الكائنية نموذج يصف البرنامج مجموعة كائنات تتفاعل، لا دوالًا منفصلة عن بياناتها. الكائن بنية تحمل حقولًا (fields أو attributes) ودوالًا (methods) تعمل على تلك الحقول، فيبقى السلوك ملاصقًا للبيانات التي يخدمها. كائنات الصنف الواحد تشترك في شكل البيانات والدوال، ويختلف كل كائن بقيمه، ولا تعمل منعزلة: يطلب أحدها من الآخر فعلًا فيُسمّى الطلب message.

Object-Oriented Programming: نموذج برمجة (programming paradigm) يقوم على الكائنات (objects) بوصفها وحدة التنظيم، بدل الدوال المنفصلة عن بياناتها.

Message: طلب تنفيذ فعل يوجّهه كائن إلى كائن آخر ليقوم بعملية ما.

جوهر الشريحة الافتتاحية أن العالم الحقيقي يُوصف بدقة كمجموعة كائنات تتفاعل؛ ومن هنا جاء النموذج، لا من ترتيب الملفات أو الشاشات.

ماذا يغطي الفصل.

  • الفرق بين البرمجة الوظيفية والكائنية.
  • المصطلحات الأساسية وضبط دلالتها.
  • أهمية النموذج الكائني وفائدتاه العملية.
  • الأعمدة الأربعة التي تجعل اللغة كائنية.
  • دعم اللغات الحديثة للنموذج، والأمثلة بلغة Java.
  • الأصناف والكائنات ومخطط UML (القسم 1.8).
  • البواني ومحددات الوصول (القسم 1.9).

خطة الشريحة 3 (الترتيب الأصلي). أربعة أقسام: Introduction on OOP، ثم Basic Principles of Object Orientation (Abstraction، Encapsulation، Polymorphism، Inheritance)، ثم Objects and Classes، ثم Constructors. الأقسام 1.8 و1.9 هنا تغطي القسمين الأخيرين، والسابقة تغطي الأولين.

Modularity الوحداتية

يُقسم البرنامج وحدات مستقلة، فيُعاد استخدام كل وحدة (reusability) في موضع آخر بلا إعادة كتابة منطقها.

Readability وضوح الكود

تنظيم يطابق تصوّر المسألة، فيرتفع وضوح الكود (code readability) ويسهل العمل المشترك (collaboration) والمراجعة.

التصميم يبدأ من الأشياء. في متجر تُذكر أولًا Store وProduct وCustomer وPayment method، ثم تُوزَّع عليها البيانات والمسؤوليات؛ فالأصناف تسبق الجداول والشاشات في التفكير.

💡
قاعدة عمل: قبل أول سطر كود اسأل: ما الكائنات هنا؟ ثم ما بيانات كل كائن؟ ثم ما الأفعال التي ينفذها؟

1.2 الكائن والصنف Object, Class, Attribute, Method

الصنف قالب (template وblueprint) لا يحمل قيم كائن بعينه، والكائن نسخة (instance) تحمل الحالة الحالية، وإنشاء النسخة يُسمّى instantiation.

Object الكائن

شخص أو مكان أو شيء، له identity فريدة تميزه عن غيره، وstate، وbehavior.

Class الصنف

فئة كائنات متشابهة، تصف شكل بياناتها ودوالها. يعرّف نوع الحقل، ولا يحمل قيمة حقل كائن بعينه.

Attribute الخاصية

متغير للكائن أو الصنف يصف خاصية من خصائصه. الشريحة تجمع في العنوان ثلاث تسميات: Data - Properties - Attributes، فالمرادفات الدقيقة field وproperty (وstate عند الحديث عن القيم)، وdata الكلمة العامة في العنوان نفسه.

Method الدالة

فعل ينفذه الكائن؛ مرادفاته operation وfunction وbehavior. تقابل الدالة الحرة في النموذج الوظيفي.

State الحالة

القيم الحالية للحقول في لحظة معينة؛ تُتميّز الدائرة بـ radius والمستطيل بـ width وheight.

Behavior السلوك

الأفعال المعرّفة بالدوال، واستدعاؤها طلب تنفيذ؛ مثل getArea() وgetPerimeter() وsetRadius().

مثال السيارة. داخل صنف car خصائص fuel وmaxspeed، ودوال refuel() وgetFuel() وsetSpeed() وgetSpeed() وdrive()؛ ويُضاف إليها move() وcalculateVehicleAge().

جدول: العضو، النوع، الدور
العضوالنوعالدور
NameStringاسم السيارة، يُقرأ بـ getName() ويُكتب بـ setName(String)
MaxSpeedIntegerأقصى سرعة، تُضبط بـ setSpeed()
PriceFloatالسعر، قيمة تخص الكائن لا الصنف
ModelIntegerالطراز (وللشريحة حقل منفصل لسنة الصنع Build year).

ثلاث نسخ من القالب نفسه، تختلف بالقيم لا بالشكل:

جدول: السيارة، السعر، اللون، السنة
السيارةالسعراللونالسنة
AAA10KOrange2015
BBB15KBlue2018
CCC45KGreen2015

لماذا يهم. هذا التفريق أساس كل ما بعده: القالب يُكتب مرة في الصنف، والقيم تتعدد في الكائنات، وعليه يقوم التغليف ومثال Circle في القسم 1.8.

🎯
محور اختبار: أين تعيش قيمة Price؟ في الكائن لا في الصنف. والصنف يعرّف نوع الحقل فقط، وكل كائن يحمل قيمته المستقلة.
جرّب نفسك: هل يحمل الصنف قيمة radius لكائن بعينه؟
لا. الصنف يعرّف وجود الحقل ونوعه، والكائن وحده يحمل القيمة؛ ولذلك يبقى تغيير كائن من كائنات الصنف بلا أثر على أخوته.

1.3 البرمجة الوظيفية مقابل الكائنية Functional Programming vs OOP

الفكرة. النموذج الكائني يجمّع البيانات والسلوك داخل كائن هو instance من صنف، فلا تُفصل الحقول عن دوالها. أما البرمجة الوظيفية فتبني البرنامج من دوال: دخل ثم خرج، كما في رسم الشريحة.

x  →  f  →  f(x)

Pure function: دالة بلا آثار جانبية (side-effects)، خرجها تابع لدخلها وحده.

First-class citizen: الدالة عندما يمكن تمريرها وسيطًا أو إرجاعها نتيجة، فتُعامل كأي قيمة أخرى.

جدول مقارنة: البرمجة الكائنية، البرمجة الوظيفية
البرمجة الكائنيةالبرمجة الوظيفية
وحدة التنظيم: الكائنوحدة التنظيم: الدالة
البيانات والسلوك داخل الكائن نفسهالدالة تأخذ دخلًا وتعيد خرجًا
الحالة محفوظة في الحقولالتركيز على دوال نقية بلا حالة خارجية

لماذا يهم. الفرق بنيوي لا تفضيلي: وحدة التنظيم في النموذج الكائني هي الكائن (بيانات + سلوك)، وفي الوظيفي هي الدالة؛ والمطلوب في الاختبار تمييز النموذج لا الحكم على أحدهما.

1.4 النمذجة والمتطلبات Modeling and Requirements

الفكرة. نستخدم النموذج الكائني لصنع حل هو model يحقّق حاجة الزبون والعمل. والنموذج تبسيط للواقع، لأن تعقيد النظام كله لا يُستوعب دفعة واحدة، وهو وصف مكتمل من منظور واحد لا من كل المناظير.

Model: تبسيط للواقع، ووصف مكتمل للنظام من منظور معيّن.

ننمذج كي نُبصر البنية (visualize) ونحدّدها (specify) ونبنيها (construct) ونوثّقها (document)، سواء في المعمارية أو السلوك.

جدول: نوع المتطلب، ماذا يجيب، أمثلة
نوع المتطلبماذا يجيبأمثلة
Functionalماذا يفعل النظامالميزات والوظائف ومتطلبات المستخدم (user requirements)
Nonfunctionalكيف يكون النظامquality attributes وخصائص المنتج وتوقعات المستخدم

خمسة مبادئ للنمذجة.

  1. أحسن اختيار النموذج؛ فاختياره يغيّر تحليل المشكلة وتصميم الحل.
  2. النموذج نفسه يُعرض بمستويات دقة متفاوتة.
  3. أفضل النماذج متصلة بالواقع: بسّط ولا تخفِ تفصيلًا مهمًا.
  4. لا يكفي نموذج واحد لنظام غير تافه.
  5. قارِب النظام بمجموعة صغيرة من نماذج شبه مستقلة.

ورسم الشريحة يحذّر من فجوة التواصل: كل حلقة تُدخل تفسيرًا جديدًا، فقد يصل المنتج إلى ما لم يرده المستخدم.

اقتراح المهندس التجاري
←
تحديد مدير المشروع
←
تصميم الفريق
←
البرمجة والتركيب
🎯
محور اختبار: احفظ التعريفين: تبسيط للواقع، ووصف مكتمل من منظور معيّن. وصنّف المتطلب: «إتمام تحويل مصرفي» وظيفي، و«زمن استجابة أقل من ثانية» غير وظيفي.

لماذا يهم. المتطلبات مدخل النموذج؛ ومتطلب فُهم خطأً يُنتج صنفًا في غير موضعه. لذلك تُصنَّف المتطلبات قبل التصميم.

1.5 نمذجة EER مقابل OO

الفكرة. نموذج Enhanced Entity–Relationship (أو Extended Entity–Relationship) يعرّف علاقات العمل لقاعدة علائقية، بينما OO data modeling يصمّم الكود. في رسم EER تبقى البيانات في جهة، والدوال Function 1 وFunction 2 وFunction 3 منفصلة عنها؛ وفي النموذج الكائني يحمل كل كائن بياناته ودواله معًا.

جدول مقارنة: في النموذج الكائني، في نموذج EER
في النموذج الكائنيفي نموذج EER
Class: كيان ذو دور واضح، له state وbehavior وidentityEntity type: لبنة تصف بنية البيانات
Object: نسخة معيّنة من صنفEntity instance: ظهور واحد لكيان
Association: دور الكائن داخل ارتباطRelationship: ارتباط بين كيانات
وراثة الخصائص ووراثة السلوكوراثة الخصائص فقط، بلا تمثيل للسلوك

مثال الشريحة: PERSON بحقول NAME وPID وAGE، ثم STUDENT ومعه GPA، وEMPLOYEE ومعه SALARY.

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".
⚠️
لا تخلط المخططين: هذا مخطط الشريحة 17 التوضيحي (فيه color ولا getPerimeter)؛ والكود في القسم 1.8 مبني على مخطط الشريحة 33، وفيه radius فقط مع getArea وgetPerimeter وsetRadius وgetRadius.
🎯
محور اختبار: أقوى فرق بين النموذجين أن OO يمثّل السلوك ويورّثه، وEER يورّث الخصائص فقط. والنمذجة الكائنية تُنجز غالبًا بـ UML.

لماذا يهم. اختيار النموذج يحدّد ما يمكن تمثيله: EER لقاعدة علائقية بلا سلوك، وOO لكود فيه حالة وسلوك؛ ومخطط الدائرة هنا أول تطبيق عملي للنموذج الكائني.

1.6 التغليف والتجريد Encapsulation and Abstraction

الفكرة. أربعة مبادئ تجعل اللغة كائنية، وهي أعمدة 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
BankAccount.java
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؟
التجريد قرار بما يُظهَر وما يُخفى من التفاصيل عند التصميم، والتغليف آلية تنفيذية تحمي الحالة بحقول خاصة وواجهة عامة. الأول يبسّط التصوّر، والثاني يمنع الوصول غير المنضبط.

1.7 الوراثة وتعدد الأشكال Inheritance and Polymorphism

الوراثة. آلية تنتقل بها خصائص صنف الأب (attributes وmethods) إلى صنف ابن (subclass)، فيُعاد استخدام المشترك. والابن يتخصص بزيادة ميزات أو تعديلها، وعدة أصناف تُعمَّم في أب واحد يجمع المشترك؛ والتخصيص والتعميم يبنيان شجرة أصناف.

Simple بسيطة

أب ثم ابن واحد يتخصص فيه.

Hierarchical هرمية

أب واحد وعدة أبناء يشتركون في خصائصه.

Multilevel متعددة المستويات

سلسلة أجيال: أب ثم ابن ثم حفيد.

مثال التمديد. JDK 1.8 يمتد إلى JDK 1.9 بثلاث خطوات، والأب لا يُعاد بناؤه من الصفر:

  1. جمع ميزات الكائن القائم.
  2. إضافة ميزات جديدة.
  3. تحديث القائم عند الحاجة.

تعدد الأشكال. للكيان أكثر من شكل، ومن هنا poly. تُصمَّم الكائنات لتشترك في سلوك، فتُطبَّق الدالة نفسها على كائنات من أصناف مختلفة؛ والوراثة تمكّن التعدد، والتعدد يزيد الكود العام (generic code).

في الشريحة صنف Animal فيه picture وfood وhunger وboundaries وlocation، ودوال makeNoise() وeat() وsleep() وroam()؛ والأبناء Lion وHippo وTiger وDog وCat وWolf.

Animal.java
class Animal {
    void makeNoise() { }
}

class Dog extends Animal {
    void makeNoise() { /* barking */ }
}

class Cat extends Animal {
    void makeNoise() { /* Meow */ }
}

الاسم واحد في الأصناف الثلاثة، والتنفيذ يختلف بحسب الصنف الذي يستقبل الاستدعاء.

Test.java · excerpt
// مقتطف: يُكتب داخل main
Animal a = new Dog();
a.makeNoise();   // barking

المرجع من نوع Animal والكائن من نوع Dog، فيُنفَّذ سلوك الكلب.

Animal
↑
Lion
Hippo
Tiger
Dog
Cat
Wolf
🎯
محور اختبار: التعدد يظهر عند الاستدعاء عبر مرجع Animal؛ يُختار التنفيذ وقت التشغيل بحسب نوع الكائن الفعلي (method overriding)، لا بحسب نوع المرجع.

لماذا يهم. الوراثة تنقل السلوك المشترك مرة واحدة، والتعدد يجعل makeNoise() شكلًا مختلفًا عند Dog وCat دون تغيير اسم الدالة.

1.8 الأصناف والكائنات ومخطط UML Class Diagram

الفكرة. كائنات النوع الواحد تُعرَّف بصنف مشترك هو قالب حقولها ودوالها. والصنف يوفّر بواني (constructors) تُستدعى بـ new لإنشاء الكائن وتهيئة حقوله، ويمكن إنشاء نسخ كثيرة مستقلة.

ترميز مخطط الصنف.

  • الحقل: dataFieldName: dataFieldType
  • الباني: ClassName(parameterName: parameterType)
  • الدالة: methodName(parameterName: parameterType): returnType
  • الكائن: name: ClassName ثم قيم الحقول.
Circle
------------------------------------------
- radius: double
------------------------------------------
+ Circle()
+ Circle(newRadius: double)
+ getArea(): double
+ getPerimeter(): double
+ setRadius(newRadius: double): void

ومنه ثلاث نسخ: circle1 بنصف قطر 1، وcircle2 بنصف قطر 25، وcircle3 بنصف قطر 125.

وهذا الصنف تعريف لا برنامج، لأنه بلا main:

Circle.java
public class Circle {
    private double radius;

    public Circle() {
        radius = 1;
    }

    public Circle(double newRadius) {
        radius = newRadius;
    }
}

بانيان بالاسم نفسه ووسطاء مختلفين: هذا هو التحميل الزائد (overloading).

Circle.java · تكملة
    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 ليبقى الباني متاحًا خارج الحزمة.
TestCircle.java
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 تستدعيه لتشغيل البرنامج.

Output
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 بعد إنشائه.

1.9 البواني ومحددات الوصول Constructor and Access Modifier

الباني. نوع خاص من الدوال لتهيئة الكائن عند new، وله ثلاث سمات تُحفظ:

  1. اسمه اسم الصنف نفسه.
  2. بلا نوع إرجاع، ولا حتى void.
  3. يُستدعى بـ new عند إنشاء الكائن، ودوره تهيئة حقوله.

ويُحمَّل تحميلاً زائداً (overloaded) لبناء كائنات بقيم ابتدائية مختلفة، كما في Circle() وCircle(double). وإن لم يُعرَّف أي باني، توفّر اللغة ضمنياً بانياً عاماً بلا وسطاء وجسمه فارغ، اسمه default constructor، ولا يُضاف إلا إذا لم يُكتب أي باني صراحة؛ وصنف Circle في القسم السابق كتب بانيين، فلا يوجد ثالث ضمني.

محددات الوصول. الصنف public مرئي لكل الحزم، والصنف بلا محدد (package-private) مرئي داخل حزمته فقط، حتى من ملف آخر في الحزمة نفسها. وعلى الأعضاء:

جدول: المحدد، من يراه
المحددمن يراه
publicالعالم كله: داخل الصنف وخارجه
protectedالحزمة وكل الأصناف الفرعية
default (بلا كلمة)الحزمة فقط
privateالصنف نفسه فقط، عبر دواله

وجدول الرؤية الكامل كما في الشريحة:

جدول: موقع الكود، default، public، protected، private
موقع الكودdefaultpublicprotectedprivate
الصنف نفسهنعمنعمنعمنعم
صنف في الحزمة نفسهانعمنعمنعملا
صنف فرعي في الحزمة نفسهانعمنعمنعملا
صنف فرعي خارج الحزمةلانعمنعملا
ليس فرعياً وخارج الحزمةلانعملالا

ورسم الشريحة يضع في الحزمة الأولى 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 يعبر إلى الفرع خارجها.

1.10 المصطلحات والأخطاء الشائعة Glossary and Common Pitfalls

جدول يجمع مصطلحات الفصل في مكان واحد للمراجعة السريعة.

جدول: المصطلح، المعنى بالعربية، ملاحظة / مثال
المصطلحالمعنى بالعربيةملاحظة / مثال
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.
02

Chapter 2 · 33 slides

الكائنات والأصناف

Objects and Classes

ينتقل الفصل من إنشاء الكائن واستدعاء دواله إلى تنظيم أعضائه: أيّها يخصّ الكائن وأيّها يخصّ الصنف كله، ومن يرى ماذا، وكيف تُحمى الحقول، وكيف ينتقل الكائن إلى دالة، وكيف يسمّي الكائن نفسه.

2.1 المتغيرات والثوابت والدوال الثابتة Static Variables, Constants, and Methods

لكل كائن نسخته الخاصة من instance variable، ولا تُستدعى instance method إلا على كائن موجود عبر متغير مرجع. أما العضو المعلَن بـstatic فنسخة واحدة في الذاكرة يشترك فيها كل كائنات الصنف، ويُستخدم باسم الصنف قبل وجود أي كائن أو بعده. ويجوز الوصول إليه عبر مرجع كائن أيضاً (c1.numberOfObjects) لأنه مشروع، لكن المستحسن باسم الصنف لأنه ليس ملك كائن بعينه.

static: معدّل يربط العضو بالصنف لا بالمثيل، ويُسطَّر في UML للدلالة عليه. والمثيل instance كائن واحد ناتج عن instantiate.

Static Variable متغير ثابت

خانة واحدة في الذاكرة للصنف كله؛ تعديلها من أي كائن يظهر عند الجميع.

Static Constant ثابت مشترك

مثل static final int MAX_RADIUS = 100;؛ يُقرأ باسم الصنف ولا يُعاد إسناده.

Static Method دالة ثابتة

غير مربوطة بكائن، تُستدعى باسم الصنف، ولا تعتمد على بيانات كائن بعينه.

مخطط الصنف في UML يكشف الفرق بالتسطير:

Circle
------------------------------------
  radius          : double
  numberOfObjects : int         <- static (underlined)
------------------------------------
  getArea()            : double
  getNumberOfObjects() : int    <- static (underlined)
جدول مقارنة: الوجه، العضو المثيلي instance، العضو الثابت static
الوجهالعضو المثيلي instanceالعضو الثابت static
عدد النسخنسخة لكل كائننسخة واحدة للصنف كله
متى يوجدبعد new فقطقبل أي كائن وبعده
الاستدعاءc1.getArea()Circle.getNumberOfObjects() (أو عبر مرجع، والأفضل باسم الصنف)
في UMLبلا تسطيرمسطَّر
CircleWithStaticMembers.java
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 خانة خاصة بكل كائن.

TestCircle.java · excerpt
// مقتطف: يُكتب داخل 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 وحدها، والعداد خانة واحدة مشتركة لم تُعدَّل.

2.2 قيود العضو الثابت Static Method Cautions

الدالة المثيلية تصل إلى الحقول والدوال المثيلية والثابتة معاً، لأن معها كائناً هو this ومعها الصنف. أما الدالة الثابتة فلا كائن مستقبِل لها عند الاستدعاء، فلا تصل إلى عضو مثيلي مباشرةً، بل عبر مرجع كائن صريح مثل a.i وa.m1().

الاستدعاء الثابت: استدعاء يحدث بلا مستقبِل كائن؛ ولهذا يرفض المترجم استخدام عضو مثيلي من سياق static إلى أن يُنشأ كائن ويُستخدم مرجعه.

جدول: من السياق، دالة مثيلية، حقل مثيلي، دالة ثابتة، حقل ثابت
من السياقدالة مثيليةحقل مثيليدالة ثابتةحقل ثابت
داخل دالة مثيليةنعمنعمنعمنعم
داخل دالة ثابتةلا مباشرةً (نعم عبر a.m1())لا مباشرةً (نعم عبر a.i)نعمنعم
A.java
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 فثابتان واستخدامهما مشروع.

التصحيح بإنشاء كائن ثم الوصول من مرجعه:

A.java · main بعد الإصلاح
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 فثابتان في الحالين.

2.3 محددات الوصول Visibility Modifiers

visibility modifier ويسمّى أيضاً access specifier يحدد من يرى الصنف وأعضاءه. على الصنف: public يجعله متاحاً لأي كود في أي حزمة، وبلا معدّل يبقى محصوراً في حزمته. وعلى العضو: public يُرى من داخل الصنف وخارجه، وprivate من دوال الصنف نفسه فقط، وبلا معدّل يُرى داخل الحزمة كلها.

الرؤية: حكم على مكان الكود لا على «صاحب الكائن»؛ فكود داخل الصنف يرى أعضاءه الخاصة حتى عبر مرجع لكائن آخر من الصنف نفسه.

جدول: المعدّل، داخل الصنف، الحزمة نفسها، حزمة أخرى
المعدّلداخل الصنفالحزمة نفسهاحزمة أخرى
publicنعمنعمنعم
بلا معدّلنعمنعملا
privateنعملالا

مثال الشريحة: الصنف C1 في الحزمة p1 فيه x عام وy بلا معدّل وz خاص، ودوال m1 عامة وm2 بلا معدّل وm3 خاصة. من يصل إلى ماذا من الكائن o:

جدول مقارنة: العضو، C2 في p1، C3 في p2
العضوC2 في p1C3 في 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.

C.java
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؛ والوصول مشروع لأن الكود داخل الصنف المالك.

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

2.4 تغليف حقول البيانات Data Field Encapsulation

جعل الحقول private يحميها ويسهّل صيانة الصنف: العميل لا يعتمد على تمثيل الحقل الداخلي، والصنف يرفض القيم غير المقبولة في مكان واحد. من يحتاج القراءة يأخذ getter، ومن يحتاج التعديل يأخذ setter.

getter / accessor: public returnType getPropertyName()، وإن كان النوع boolean فالعُرف public boolean isPropertyName(). setter / mutator: public void setPropertyName(dataType value). وفي UML الإشارة - تعني private و+ تعني public.

Getter دالة قراءة

تعيد قيمة الحقل دون كشف موقعه أو طريقة تخزينه، مثل getRadius() وisEmpty().

Setter دالة تعديل

تفحص القيمة قبل إسنادها؛ فهي نقطة التحقق الوحيدة، مثل رفض نصف القطر السالب.

CircleWithPrivateDataFields
-----------------------------------------------
- radius: double
- numberOfObjects: int          <- private + static
-----------------------------------------------
+ Circle()
+ Circle(newRadius: double)
+ getRadius(): double
+ setRadius(newRadius: double): void
+ getNumberOfObjects(): int     <- static
+ getArea(): double
CircleWithPrivateDataFields.java
public class CircleWithPrivateDataFields {
  private double radius = 1;
  private static int numberOfObjects = 0;

  public CircleWithPrivateDataFields() {
    numberOfObjects++;
  }

  public CircleWithPrivateDataFields(double newRadius) {
    radius = newRadius;
    numberOfObjects++;
  }

الحقلان خاصان: radius لكل دائرة، والعداد مشترك بين الجميع.

CircleWithPrivateDataFields.java · تكملة
  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.

TestCircleEncapsulation.java · excerpt
// مقتطف: يُكتب داخل 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.

2.5 تمرير الكائنات إلى الدوال Passing Objects to Methods

تمرير كائن إلى دالة هو تمرير مرجعه لا الكائن نفسه. والأسلوب pass-by-value ينسخ القيمة الممرَّرة: إن كانت عدداً نُسخ العدد، وإن كانت مرجعاً نُسخت نسخة من العنوان، ويبقى الكائن واحداً على الـheap.

activation record: إطار الدالة على الـstack، يحوي معاملاتها ومتغيراتها المحلية؛ في المخطط إطار printArea فيه times نسخة مستقلة، وc نسخة من المرجع تشير إلى كائن main نفسه.

نسخ القيمة copy of value

العدد الممرَّر نسخة؛ تغييره داخل الدالة لا يمس متغير المستدعي.

نسخ المرجع copy of reference

العنوان يُنسخ، والكائن واحد؛ تعديل حقوله يظهر عند المستدعي.

إعادة الإسناد reassignment

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)
Test.java
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 ممكن لأنه بلا معدّل والصنفان في الحزمة الافتراضية نفسها.

Test.java · الدالتان
  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. القاعدة: تعديل حقول الكائن يظهر، وتعديل المعامل نفسه لا يظهر.

لماذا يهم: أي دالة تستقبل كائناً وتعدّل حقوله تعدّل كائن المستدعي، وهذا يفسّر كثيراً من النتائج التي تبدو غير متوقعة في أسئلة التتبع.

💡
طريقة تتبّع: ارسم إطارين على الورق: إطار المستدعي وإطار الدالة. ما يُرسم داخل الكائن على الـheap يبقى بعد العودة، وما يُكتب في الإطار يُمحى.
جرّب نفسك: لو استُبدل جسم swap2 بـCircle temp = x; x = y; y = temp; فما الناتج؟
يبقى 1.0 و2.0 كما بعد swap1، لأن التبديل طال نسخ المراجع المحلية لا حقول الكائنات.

2.6 المرجع this The this Reference

this يشير إلى الكائن الذي استُدعيت عليه الدالة المثيلية. وthis.radius وradius متكافئان ما دام اسم الحقل غير محجوب، أما الاستعمال الحاسم فتمييز حقل الصنف من وسيط يحمل الاسم نفسه.

this: مرجع ثابت نحو المستقبِل الحالي؛ يصلح لقراءة حقول الكائن واستدعاء دواله مثل this.getArea()، ولا معنى له في دالة static لأنه لا كائن مستقبِل في سياق static.

Circle.java
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().

Circle.java · الحقل المحجوب
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 يحتاج كائناً مستقبِلاً، والدالة الثابتة تُستدعى بلا كائن، فيرفض المترجم ذلك.

2.7 جدول المصطلحات Terminology

جدول: المصطلح، المعنى بالعربية، ملاحظة / مثال
المصطلحالمعنى بالعربيةملاحظة / مثال
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

2.8 أخطاء شائعة Common Mistakes

جدول مقارنة: الخطأ، الصواب
الخطأالصواب
استدعاء 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

أهم ما يُسأل عنه

  • تمييز المسطَّر في UML: عضو مشترك، وغير المسطَّر: نسخة لكل كائن.
  • تتبع 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: نسختا المرجع تشيران إلى كائن واحد، ونسختا العدد مستقلتان.
03

Chapter 3 · 60 slides

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

Inheritance and Polymorphism

الابن يرث من أب واحد بـextends، ويخصّص بوراثة الأعضاء أو بإعادة تعريف الدوال، ثم يقرّر نوع الكائن الفعلي — لا نوع المرجع — أي نسخة تُنفَّذ.

3.1 الوراثة وعلاقة is-a

Inheritance: اشتقاق صنف جديد من صنف موجود، فيرث بياناته ودواله ويضيف أو يعدّل. الصنف الموجود superclass (أو parent class / base class)، والمشتق subclass (أو child class).

دائرة ومستطيل يتشاركان اللون والامتلاء وتاريخ الإنشاء، ويختلفان في نصف القطر أو في العرض والارتفاع. نضع المشترك في Shape، ونشتق منه Circle وRectangle بدل نسخ الحقول في كل صنف.

الوراثة السليمة علاقة is-a: الدائرة شكل، وليست «الشكل دائرة». في مخطط UML (الشريحتان 4 و6) يظهر الأب أعلى الابن، والسهم المثلث يتجه من الابن إلى الأب.

Shape
↑
Circle
Rectangle
Vehicle.java
class Vehicle {
    protected String plate;
    public void start() { System.out.println("Engine on"); }
}

class Car extends Vehicle {
    private int doors;   // إضافة الابن
}

الابن لا يعيد كتابة plate ولا start؛ يرثهما ويضيف ما يخصّه.

لماذا يهم: إعادة استخدام الشفرة وتخصيصها في مكان واحد. تعديل دالة في الأب ينعكس على كل الأبناء، وتصحيح الخطأ يتم مرة واحدة.

🎯
محور اختبار: جافا لا تدعم multiple inheritance للأصناف: للابن أب واحد فقط. مثال الشريحة شاحنة خفيفة ترث من سيارة وشاحنة معاً، والوراثة المتعددة تفرض حلّ تصادم الأسماء بين الأبوين؛ جافا تتجنّب هذا العبء وتوفّر جوانب منه عبر interfaces.
س: لماذا لا نجعل Pickup يمتد من Car وTruck؟
ج: لأن جافا وراثة أحادية للأصناف، والأبوان قد يحملان عضواً بالاسم نفسه فيتوجّب على المترجم قاعدة لحلّ التصادم. الحل: يرث Pickup من أب واحد، ويأخذ السلوك المشترك من واجهة.

3.2 الصنف الأب والصنف الابن Superclass and Subclass

extends: الكلمة المحجوزة التي تربط الابن بالأب. بواني الأب لا تُورَّث؛ تُستدعى صراحةً بـsuper أو ضمناً عند البناء.

Shape يخزّن color وfilled وdateCreated، ويوفّر بانياً بلا وسطاء وبانياً باللون والامتلاء. الحقول private، والقيمة الافتراضية للون "white"، والشكل لا يُملأ حتى يُضبط.

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

Circle.java
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 الذي ينادي باني الأب.

3.3 مرجع super وصحّة بواني الأب The super Reference

super(...): استدعاء باني الأب، ويجب أن يكون أول جملة في باني الابن. غياب الاستدعاء الصريح يعني super() ضمنياً، بشرط وجود باني أب بلا وسطاء.

إن لم يُعرّف الأب أي بانٍ، أدرج المترجم باني default فارغاً فينجح الاستدعاء الضمني. إن عرّف الأب بواني بوسطاء فقط فلا باني فارغ، ويفشل super() الضمني بخطأ ترجمة.

جدول: الشفرة، الحكم، السبب
الشفرةالحكمالسبب
الأب فيه Shape() والابن Circle() بلا superصحيحتعادل super();
Circle(double radius) يسند نصف القطر فقطصحيحsuper(); ثم الإسناد
الأب فيه Shape(String, boolean) فقط والابن Circle()خطألا باني فارغ يُستدعى
الأب بلا أي بانٍ والابن Circle()صحيحباني default في الأب
أول سطر في الابن super("Red", true);صحيحاستدعاء صريح مطابق
Circle بلا بوانٍ والأب فيه Shape()صحيحالباني المُدرج يستدعي super()
Circle.java
public Circle() {
    super("Red", true); // يجب أن يسبق أي جملة أخرى
}

نداء صريح مطابق لباني الأب بالوسيطين؛ لا حاجة للإسناد اليدوي بعد ذلك.

لماذا يهم: الابن مسؤول عن بناء أبيه؛ بانٍ لا يجد باني أب مطابقاً لا يُترجم، وترتيب الجملة الأولى شرط يسبق أي إسناد.

⚠️
خطأ ترجمة: this.radius = radius; قبل super(...) غير مسموح؛ استدعاء باني الأب أول جملة دائماً. ولا يجتمع this(...) وsuper(...) في الباني نفسه.
🎯
محور اختبار: أسئلة «هل الشفرة صحيحة؟» تُحسم بوجود باني أب بلا وسطاء. أكثر أخطاء الترجمة في الفصل من باني أب غير موجود أو من وضع super بعد جملة.
س: Shape فيه باني واحد بوسطاء، وCircle() فارغ. هل تُترجم؟
ج: لا. الاستدعاء الضمني super() لا يجد باني أب بلا وسطاء. الحل: super(color, filled) صريحاً أو إضافة باني فارغ للأب.

3.4 سلسلة البناء وتتبع التنفيذ Constructor Chaining

Constructor chaining: إنشاء كائن يستدعي بواني كل الأجداد على سلسلة الوراثة، من الأعلى إلى الأسفل، قبل أن يعمل جسم باني الابن.

عند دخول بانٍ يبدأ بـthis(...) ينتقل إلى بانٍ آخر في الصنف نفسه ولا يستدعي الأب بعد؛ والباني الذي لا يبدأ بـthis يبدأ بـsuper صريحاً أو ضمنياً.

Faculty.java
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)، فيؤجّل نداء الأب إلى الباني الآخر.

Person.java
class Person {
    public Person() {
        System.out.println("(1) Person's no-arg constructor is invoked");
    }
}

جذر السلسلة: ينفّذ أولاً لأنه لا أب له فوقه.

ترتيب النداء ثم الطباعة (خطوات الشرائح 19–27):

  1. البدء من main ثم new Faculty().
  2. دخول Faculty() وفي أوله super() نحو Employee().
  3. Employee() يبدأ بـthis(String) لا بـsuper.
  4. Employee(String) يستدعي ضمنياً Person().
  5. تنفيذ طباعة Person ثم الرجوع.
  6. طباعة الوسيط في Employee(String).
  7. الرجوع إلى Employee() وطباعة السطر (3).
  8. الرجوع إلى 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

لماذا يهم: جسم الباني لا يعمل قبل اكتمال بناء الأب، وأي طباعة في الابن تظهر بعد طباعات الأجداد.

س: لماذا تُطبع (1) قبل (2) مع أن this ظاهرة قبل أي طباعة في Employee()؟
ج: Employee(String) ينادي Person() أولاً فيُطبع (1)، ثم يُطبع الوسيط (2)، ثم يعود فيُطبع (3)، ثم (4).

3.5 إعادة التعريف والتحميل الزائد Overriding vs Overloading

الابن يصنع أربع حركات: يرث أعضاء الأب، ويضيف حقولاً (radius)، ويضيف دوالاً (getArea وgetDiameter وgetPerimeter وprintCircle)، ويعيد تعريف دالة الأب (toString).

Method overriding: دالة غير ساكنة (instance method) في الابن تطابق اسم دالة الأب وأنواع وسطائها. التعليق @Override يعلن النية؛ فإن لم توجد دالة مطابقة فشل الترجمة.

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

Circle.java — بقية الصنف
    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
الوجهإعادة التعريف overridingالتحميل الزائد overloading
التوقيعمطابق تماماًوسطاء مختلفة
المكانبين أب وابنفي الصنف نفسه غالباً
وقت الاختياروقت التشغيل حسب الكائنوقت الترجمة حسب الوسائط
الأثريستبدل نسخة الأبتبقى النسختان

مثال الشريحة: الصنف B فيه p(double) تطبع i * 2، والابن A يعرّف إمّا p(double) أو p(int). الاستدعاءان a.p(10) وa.p(10.0):

جدول مقارنة: البند، الابن يعرّف p(double)، الابن يعرّف p(int)
البندالابن يعرّف p(double)الابن يعرّف p(int)
العلاقةإعادة تعريف لنسخة الأبتحميل زائد؛ نسخة الأب باقية
a.p(10)توسيع إلى double ثم نسخة الابن: 10.0تطابق p(int): 10
a.p(10.0)نسخة الابن: 10.0نسخة الأب: 20.0
OverrideTest.java
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
    }
}

التوقيع نفسه، فالنسخة المنفَّذة نسخة الابن في الحالتين.

OverloadTest.java
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 أي إخفاء للنسخة الأصلية لا استبدال لها، والاختيار يتبع نوع المرجع لأن الربط ساكن.
StaticHiding.java
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 يختار نسخة الأب.

3.6 محددات الوصول Access Modifiers

Access modifiers: من الأضيق إلى الأوسع private ثم default (بلا كلمة) ثم protected ثم public. الرمز # في UML يعني protected.

جدول: المعدّل، الصنف، الحزمة، ابن (الحزمة نفسها)، ابن (حزمة أخرى)، صنف غير ابن في حزمة أخرى
المعدّلالصنفالحزمةابن (الحزمة نفسها)ابن (حزمة أخرى)صنف غير ابن في حزمة أخرى
publicYesYesYesYesYes
protectedYesYesYesYesNo
defaultYesYesYesNoNo
privateYesNoNoNoNo

عمودا «الحزمة» و«صنف غير ابن في حزمة أخرى» يقيسان الوصول من صنف غير ابن: الأول داخل الحزمة والثاني خارجها، فـ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 داخل p1x وy وz وm()، لا u
C4 extends C1 في p2x و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 في الجدول.

3.7 المعدّل النهائي وصنف الجذر final and Object

final يجمد ثلاثة أشياء مختلفة، وكل صنف في جافا سليل java.lang.Object؛ فإن لم تُكتب extends فالأب هو Object.

متغيّر final

قيمته ثابتة لا تُسند بعد التهيئة: final static double PI = 3.14159;

صنف final class

لا يُمدَّد: final class Math. أي محاولة extends Math خطأ ترجمة.

دالة final method

لا يعيد الابن تعريفها؛ حماية سلوك لا يجوز تغييره في السلسلة.

toString(): تُرجع تمثيلاً نصياً للكائن. التنفيذ الافتراضي في Object: اسم الصنف ثم @ ثم عدد يمثل الكائن.

ToStringDemo.java · excerpt
// مقتطف: يُكتب داخل main
Loan loan = new Loan();
System.out.println(loan.toString());
System.out.println(loan); // تستدعي toString ضمنياً

المخرج على الشريحة: Loan@15037e5؛ السطران يطبعان النص نفسه.

Circle.java
class Circle { }                    // الأب الفعلي: Object
// الصورة المكافئة نفسها، ولا تُكتب مع السطر السابق:
// class Circle extends Object { }

الشكلان متكافئان؛ الشكل الأول لا يلغي وجود الأب الضمني.

🎯
محور اختبار: println على كائن بلا toString خاصة يطبع صيغة Class@hash لا محتوى الحقول؛ والمطلوب عادة إعادة تعريفها كما فعل Shape وCircle.

لماذا يهم: إعادة تعريف toString قرار مقروئية وأداة تنقيح، وfinal قرار تصميم يمنع تخصيصاً غير آمن في السلسلة.

س: ما أجزاء النص الافتراضي لـtoString؟
ج: اسم الصنف، ثم @، ثم عدد يمثل الكائن، مثل Loan@15037e5.

3.8 المراجع متعددة الأشكال Polymorphism and Dynamic Binding

Polymorphism: إشارة مرجع الأب إلى كائن ابن، فيُختار عند التشغيل النسخة المعاد تعريفها حسب نوع الكائن لا نوع المرجع. المرجع الذي يبدّل نوع الكائن عبر الزمن polymorphic reference، والربط المؤجَّل dynamic binding.

القط حيوان، فيجوز لمرجع Animal أن يشير إلى كائن Cat؛ كل الحيوانات تصدر صوتاً، لكن لكل نوع صوته.

PolymorphismDemo.java · excerpt
// مقتطف: يُكتب داخل 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): نوع العناصر هو الأب، وكل خانة كائن ابن.

AnimalArray.java · excerpt
// مقتطف: يُكتب داخل 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().

Wolf.java
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. نوع الكائن ذئب، لكن الدالة غير المعاد تعريفها تبقى للأب.

3.9 جدول المصطلحات Glossary

جدول: المصطلح، المعنى بالعربية، ملاحظة أو مثال
المصطلحالمعنى بالعربيةملاحظة أو مثال
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]

3.10 أخطاء شائعة Common Mistakes

⚠️
القاعدة الجامعة: البواني تُستدعى ولا تُورَّث، و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
قسر عكسي بلا حاجة، أو إسناد ابن إلى أب مع قسرالتوسيع إسناد مباشر؛ التضييق وحده يحتاج القسر

3.11 خريطة الشرائح Slide Map

جدول مقارنة: الشرائح، القسم
الشرائحالقسم
1–2خريطة الفصل وأهدافه داخل الأقسام 1–8
3–73.1 الوراثة وعلاقة is-a، بما فيها منع الوراثة المتعددة
8–103.2 Shape وCircle وextends وباني الابن
11–173.3 super وأحكام صحّة البواني
18–273.4 سلسلة البناء وتتبع Faculty
28–363.5 إضافات الابن، toString، إعادة التعريف مقابل التحميل، super للدوال، قيود الخاص والساكن
37–473.6 جدول الوصول وأمثلة default وprivate وprotected وpublic
48–493.6 تقوية الوصول عند إعادة التعريف ومنع إضعافه
50–523.7 final وObject وtoString
53–573.8 المراجع، التوسيع والتضييق، المصفوفة متعددة الأشكال
58–593.8 Animal وCat وDog وWolf ومخرجات الاختبار
60ختام العرض؛ لا يضيف قاعدة جديدة

أهم ما يُسأل عنه

  • علاقة is-a، ومنع وراثة الأصناف المتعددة، ودور 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 تبقى للأب.
04

Chapter 4 · 33 slides

الأصناف المجردة والواجهات

Abstract Classes and Interfaces

يكمل هذا الفصل الوراثة وpolymorphism: نُغلق النوع العام بالصنف المجرد فلا يُنشأ كائن منه، ونصف السلوك المشترك بالواجهة فتُنفَّذ في أصناف لا تربطها قرابة. النتيجة: مرجع واحد من نوع الأب أو الواجهة يجمع كائنات مختلفة، وكل ابن ملزم بتنفيذ ما يفرضه العقد.

4.1 لماذا نُبقي الصنف الأب؟ Motivation

في شجرة الوراثة يصير الأب أعمّ والابن أخصّ. إنشاء كائن من Animal غير منطقي: القط والكلب هما الكائنان الحقيقيان، وAnimal ليس شيئاً تراه في الواقع. المشكلة نفسها مع الطالب: كل طالب إما طالب بكلوريوس أو ماجستير أو دكتوراه، ولا يصح أن ينشئ أحد «طالباً عاماً» لا يمثّل أحداً.

abstract class: صنف مسبوق بالكلمة المفتاحية abstract، يمنع إنشاء كائن منه بـ new، ويبقى قابلاً للوراثة.

Animal
↑
Cat
Dog

لماذا نُبقي الأب إذن؟ لسببين لا ثالث لهما: أن ينفّذ السلوك المشترك مرة واحدة مثل setLogin وgetLogin بدل تكراره في كل ابن، وأن نتعامل مع كل الطلاب عبر النوع Student بـ polymorphism بغضّ النظر عن الابن الفعلي.

لماذا يهم: من دون أب مجرد إما نكرّر الشيفرة في كل ابن، وإما نسمح بكائن «طالب عام» لا يمثّل أحداً. التجريد هنا قرار تصميم لا مجرد قيد لغوي.

🎯
محور اختبار: لماذا نُبقي Student إن كان ممنوعاً إنشاء كائن منه؟ الجواب المطلوب: لجمع السلوك المشترك في مكان واحد، ولاستخدامه نوعاً يشير إلى أي ابن عبر polymorphism.
س: هل يكفي أن نحذف الأب ونكرّر دواله في كل ابن؟
ج: لا. التكرار يخالف مبدأ إعادة الاستخدام، ويجعل تعديل دالة مشتركة عملية في كل صنف. الأب المجرد يحتفظ بالتنفيذ الواحد، ويمنع في الوقت نفسه الكائن الذي لا معنى له.

4.2 الصنف المجرد وبناؤه Abstract Class

المنع يخص new فقط. الصنف المجرد ما زال يُورَّث، ويملك حقولاً ودوالاً ذات جسم وبنّائين. الابن غير المسبوق بـ abstract يُسمّى concrete class، مثل PhdStudent.

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

PhdStudent.java
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 أولاً ثم جسم بنّاء الابن.

لماذا يهم: الصنف المجرد يغلق النوع العام ويُبقي شيفرته للأبناء. حذف البنّاء منه يقطع سلسلة التهيئة، فتصل حقول الأب إلى الابن غير مهيّأة.

س: هل يحق للابن أن يُعلَن مجرداً وهو يرث صنفاً مجرداً؟
ج: نعم، إن لم ينفّذ كل الدوال المجردة الموروثة. عندها يبقى هو أيضاً غير قابل للإنشاء، إلى أن يُكمل أحد أحفاده التنفيذ.

4.3 الصنف المجرد نوعاً للبيانات Abstract Class as a Data Type

منع الإنشاء لا يمنع استخدام الاسم نوعاً لمرجع أو لمصفوفة. العناصر الفعلية تكون من أبناء ملموسين، والمرجع يبقى من نوع الأب.

data type: الصنف المجرد والواجهة يصلحان نوعاً مرجعياً (reference type) لمتغيّر أو مصفوفة أو معامل دالة، ولا يصلحان لإنشاء كائن.

ShapeArray.java · excerpt
// مقتطف: يُكتب داخل 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();؟
ج: الأولى فقط. الأولى تصرّح نوعاً، والثانية تحاول إنشاء كائن من صنف مجرد فتفشل عند الترجمة.

4.4 الدالة المجردة Abstract Method

كما يُصرَّح المتغيّر Person p; بلا كائن، تُصرَّح الدالة بلا جسم. Shape لا يستطيع حساب مساحة «شكل اعتباطي»، فيُترك الحساب للابن ويُجبر عليه.

abstract method: دالة مُعلنة غير معرَّفة: ترويسة كاملة بأقواس معاملاتها، ثم ; بدل الجسم بين { }، مثل public abstract void draw(int size);.

Shape.java
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 وحدها تُترك مفتوحة.

Circle.java
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 صنفاً مجرداً، وإلا فشل الترجمة. القاعدة: من يترك دالة مجردة واحدة لا يبقى ملموساً.

4.5 مخطط UML وقواعد التجريد UML and Abstract Rules

الشريحة 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، المعنى، مثال في المخطط
الرمز في UMLالمعنىمثال في المخطط
الاسم بالمائلabstractGeometricObject، getArea()
-privatecolor، filled
#protectedبنّاءا GeometricObject
+publicgetColor()، toString()
🎯
محور اختبار — اتجاه القاعدة: وجود دالة مجردة واحدة يُلزم أن يكون الصنف مجرداً. والعكس غير لازم: صنف مجرد قد يخلو من أي دالة مجردة، كما Student. والابن قد يكون مجرداً وأبوه ملموساً: Object ملموس وShape قد يكون مجرداً.
  • إن نفّذ الابن كل الدوال المجردة الموروثة فهو مكتمل concrete ويُنشأ بـ new.
  • إن ترك واحدة، فهو مجرد أيضاً ولا يُنشأ.
  • الابن الملموس ينفّذ كل الدوال المجردة حتى التي لا يستخدمها.

لماذا يهم: السؤال الخادع يقلب الاتجاه. احفظ الجملة الواحدة: «دالة مجردة ⇒ صنف مجرد» صحيحة، و«صنف مجرد ⇒ فيه دالة مجردة» خاطئة.

س: هل يجوز abstract class بلا دوال مجردة؟
ج: نعم. الغرض حينها منع new مع الإبقاء على تنفيذ مشترك وحقول مشتركة، وهو حال Student.

4.6 ما يراه نوع المرجع Class Knows Its Superclass

المرجع من نوع الأب لا يرى إلا ما عُرّف في الأب أو فوقه. الدالة المكتوبة في الابن وحده غير مرئية عبر مرجع الأب، حتى لو كان الكائن الفعلي من ذلك الابن. السبب بجملة الشريحة: الصنف يعرف صنفه الأب لا أصناف أبنائه، والمترجم ينظر إلى نوع المرجع.

Problem.java
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() ممنوعاً.

Shape.java · Star.java
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؟
ج: لا. المترجم لا يعرف الكائن الفعلي، بل نوع المرجع. لذلك لا بد من التحويل الصريح أو من رفع الدالة إلى الأب.

4.7 تعريف الواجهة وجسمها Interface

الواجهة بناء يشبه الصنف لكنه لا يحمل حالة كائن. غرضه تحديد سلوك مشترك لأصناف مترابطة أو غير مترابطة، وتُترجم إلى ملف bytecode مستقل، وتُستخدم نوعاً كما يُستخدم الصنف المجرد: مرجعاً أو مصفوفة، لا new.

interface: جسمها تصريحات دوال كل تصريح ينتهي بـ ; وبلا تنفيذ، وحقولها ثوابت فقط. في هذا المقرر كل حقل public static final وكل دالة public abstract، ويجوز حذف هذه المعدّلات لأنها مفروضة.

StockWatcher.java
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 */ }.

T.java
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 ينفّذ الواجهة.

4.8 التنفيذ بـ implements ومثال Edible

قاعدة الملف: في ملف واحد لا يُسمح إلا بنوع public واحد، وكل نوع public يوضع في ملف يحمل اسمه. الأمثلة أدناه تُقرأ على هذا الأساس.

الصنف يلتزم الواجهة بكلمة implements، ويكتب كل دوالها بالتوقيع نفسه ونوع الإرجاع نفسه. ويستطيع أن يمتد من صنف واحد وينفّذ واجهة في الوقت ذاته. في الترميز، «interface» يسم الواجهة، والخط المتقطع مع المثلث الفارغ يعني علاقة تنفيذ، بينما الخط المتصل مع المثلث يعني وراثة صنف.

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

Chicken الدجاجة

ترث Animal وتنفّذ Edible معاً: هي حيوان ويجوز أكلها.

Tiger النمر

حيوان غير قابل للأكل، فلا ينفّذ Edible ولا يملك howToEat.

Fruit الفاكهة

ينفّذ Edible ولا يكتب howToEat، لذلك هو abstract.

Apple / Orange

ابنان ملموسان لـ Fruit، يكتب كل منهما howToEat بطريقته.

Edible.java
public interface Edible {
    /** Describe how to eat */
    public abstract String howToEat();
}

العقد كله سطر واحد: دالة واحدة تُرجع وصفاً نصياً لطريقة الأكل.

Chicken.java · Fruit.java
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 محذوفة اختصاراً في الشريحة، ولا تؤثر في المثال.

Apple.java · Orange.java
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، وتنفيذ السلوك في كل ثمرة.

TestEdible.java
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 فيمرّ من الفحص.

4.9 الواجهة مقابل الصنف المجرد Interface vs Abstract Class

الاثنان لا يُنشآن بـ new ويُستخدمان نوعين. الفرق في ما يُسمح بوضعه داخل كل منهما، وفي عدد الآباء.

جدول مقارنة: الوجه، abstract class، interface
الوجهabstract classinterface
المتغيراتبلا قيد: حقول كائن أو ثوابتكلها public static final
البوانيتُستدعى من الأبناء بـ constructor chainingلا بنّاؤون، وبلا new
الدوالبلا قيد: ملموسة أو مجردةكلها public abstract وبلا جسم هنا
الوراثةextends لصنف واحدعدة واجهات بـ implements
NewClass.java · NewInterface.java
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. ولا يصدق على واجهة لم تُنفَّذ في سلسلته.

4.10 متى الواجهة ومتى الوراثة؟ When to Use an Interface

الاثنان يصوغان المشترك، والاختيار يتبع قوة العلاقة لا ذوق الكتابة.

جدول مقارنة: العلاقة، الأداة، المثال
العلاقةالأداةالمثال
is-a قوية: أب وابن واضحانوراثة أصناف extendsStaff extends Person
is-kind-of ضعيفة: الكائن يملك صفةواجهة implementsFruit implements Edible
الحاجة إلى أكثر من أبأب واحد + واجهاتChicken يرث Animal وينفّذ Edible

الوراثة Inheritance

تشارك الشيفرة: حقولاً ودوالاً بين كائنات أصناف مترابطة.

الواجهة Interface

تشارك السلوك: ترويسات الدوال فقط بين كائنات أصناف مختلفة.

قاعدة التصميم

واجهة للسلوك المشترك، ووراثة للشيفرة المشتركة.

لماذا يهم: لو جعلنا Edible صنفاً أباً لـ Fruit وChicken لاستهلك الاثنان مقعد extends الوحيد، فحُرمت الدجاجة من وراثة Animal. الواجهة لا تستهلك هذا المقعد.

س: Staff وPerson واجهة أم وراثة؟ وFruit وEdible؟
ج: الأولى وراثة أصناف لأن is-a قوية وتُشارك حقولاً وشيفرة. الثانية implements لأن is-kind-of صفة لا نسب.

4.11 جدول المصطلحات

جدول: المصطلح، المعنى بالعربية، ملاحظة / مثال
المصطلحالمعنى بالعربيةملاحظة / مثال
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
# في UMLprotectedبنّاءا GeometricObject
«interface»وسم الواجهة في UMLمع خط متقطع ومثلث فارغ

4.12 أخطاء شائعة

جدول: #، الخطأ، الصواب
#الخطأالصواب
1new على Student أو Shape أو على واجهةnew على ابن ملموس، والمرجع يجوز أن يكون من النوع المجرد أو من الواجهة
2دالة مجردة داخل صنف غير مجردإعلان الصنف نفسه abstract
3الاعتقاد أن كل صنف مجرد يحوي دالة مجردةيجوز أن يخلو منها، مثل Student
4ابن ملموس يترك getArea أو howToEat بلا تنفيذينفّذها كلها بالتوقيع ونوع الإرجاع نفسيهما، أو يُعلَن مجرداً
5حقل كائن أو بنّاء داخل واجهةثوابت مُهيّأة فقط، وبلا بنّائين
6class C extends A, Bextends لصنف واحد ثم implements لأي عدد من الواجهات
7استدعاء draw عبر مرجع Shape والدالة في Star فقطإعلان draw في Shape؛ الصنف لا يرى دوال أبنائه
8نسخ radius^2 بوصفه أُسّاًradius * radius؛ المعامل ^ هو XOR

4.13 خريطة الشرائح

جدول: الشرائح، القسم
الشرائحالقسم
1–2خريطة الفصل: أصناف مجردة، دوال مجردة، واجهات
3–51. الدافع: Animal وStudent وأبناؤه، ومنع إنشاء Student
6–82. تعريف abstract class، ومثال Student / PhdStudent
9 و183. الصنف المجرد نوع بيانات: Shape[] ومنع new Shape()
10–124. لماذا getArea مجردة، وصيغة الدالة بلا جسم، وShape / Circle
13–155. UML لـ GeometricObject وقواعد الإكمال والتجريد
16–176. ما يراه نوع المرجع: draw عبر مرجع الأب قبل التجريد وبعده
19–237. تعريف الواجهة، وجسم StockWatcher، وتكافؤ المعدّلات
24–288. StockApplet، ومخطط Edible / Animal، وTestEdible
29–319. جدول المقارنة، وتعدد الواجهات، وsubinterface، ومخطط Class2
32–3310. 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: شيفرة مشتركة بالوراثة وسلوك مشترك بالواجهة.
05

Cheat Sheet · 4 فصول

المراجعة السريعة قبل الاختبار

Java OOP — Quick Reference

جداول مركزة لا شرح جديد: معاني المعدّلات وأخطاؤها، حدود الوصول، خمس مقارنات تُسأل حرفياً، اثنتا عشرة قاعدة، وبطاقتان للأخطاء وتسلسل البناء.

1 الكلمات المفتاحية والمعدّلات Keywords & Modifiers

جدول: الكلمة، المعنى، متى تُستخدم، خطأ شائع
الكلمةالمعنىمتى تُستخدمخطأ شائع
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 مهيّأة — حذف المعدّلات من النص لا يلغيها.

2 صلاحيات الوصول Access Modifiers

جدول: المعدّل، نفس الصنف، الحزمة نفسها، الوريث (ولو في حزمة أخرى)، أي مكان
المعدّلنفس الصنفالحزمة نفسهاالوريث (ولو في حزمة أخرى)أي مكان
privateنعملالالا
default (بلا كلمة)نعمنعملالا
protectedنعمنعمنعملا
publicنعمنعمنعمنعم
💡
فخّان: C4 extends C1 في حزمة أخرى يرى public وprotected فقط؛ وC2 في الحزمة نفسها يرى default ولا يرى private.

3 التحميل الزائد مقابل إعادة التعريف Overloading vs Overriding

جدول مقارنة: العامل، Overloading، Overriding
العاملOverloadingOverriding
التوقيعالاسم نفسه وقائمة وسطاء مختلفة (عدداً أو نوعاً)الاسم نفسه وأنواع الوسطاء نفسها: التوقيع مطابق
مكان التعريففي الصنف نفسه، أو في الابن بتوقيع مختلف (p(int) أمام p(double))في الابن، لدالة موروثة مرئية وغير ساكنة
وقت القراروقت الترجمة، حسب أنواع الوسطاءوقت التشغيل (dynamic binding)، حسب نوع الكائن الفعلي
الوراثةلا تشترط وراثة، ونسخة الأب تبقى موجودةتشترط وراثة، ويُستبدل جسم الأب ويُنادى بـ super.m()
نوع الإرجاعقد يختلف مع اختلاف الوسطاء، وتغييره وحده لا يصنع تحميلاًيبقى متوافقاً مع نوع إرجاع الأب، وهو خارج التوقيع
الوسملا @Override@Override مستحسن، ويفشل الترجمة إن لم توجد دالة مطابقة
المثال والمخرجp(10) ← 10 وp(10.0) ← 20.0p(10) ← 10.0 وp(10.0) ← 10.0
🎯
محور اختبار: p(int) في الابن لا تُلغي p(double) في الأب؛ والـstatic تُخفى لا تُستبدل، وprivate لا تُعاد تعريفاً.

4 الصنف المجرد مقابل الواجهة Abstract Class vs Interface

جدول مقارنة: العامل، abstract class، interface
العاملabstract classinterface
الغرضمشاركة شيفرة وحالة، ومنع new على النوع العامعقد سلوك مشترك بين أصناف قد لا تربطها قرابة
الحالةحقول كائن أو ثوابت، بلا قيدكل الحقول public static final ومهيّأة
البوانيله بوانٍ تُستدعى من الابن بـsuper() ضمن سلسلة البناءلا بوانٍ، ولا new عليه إطلاقاً
الوراثة المتعددةextends لصنف واحد فقطالصنف ينفّذ عدة واجهات، والواجهة الفرعية subinterface تمتد من عدة واجهات
الدوالملموسة أو مجردة، بأي رؤيةpublic abstract بلا جسم في هذا المقرر
العلاقةis-a قوية: Staff extends Personis-kind-of ضعيفة: Fruit implements Edible
متى تختارحين تشترك الأصناف في حقول وشيفرة وشجرة نسبحين تشترك في فعل فقط، أو حين يلزم أكثر من «مقعد» سلوكي
💡
الفلترة السريعة: الاثنان نوع مرجعي لا يُنشأ بـnew؛ ولا شجرة واحدة للواجهات، بينما كل الأصناف تلتقي في Object.

5 العضو الثابت مقابل المثيلي static vs instance

جدول مقارنة: العامل، static، instance
العاملstaticinstance
عدد النسخنسخة واحدة لكل الصنفنسخة لكل كائن
الوصولباسم الصنف بلا كائن Circle.getNumberOfObjects()، أو عبر مرجع، والأفضل باسم الصنفعبر مرجع كائن بعد new
يصل إلى المثيليلا مباشرةً، إلا عبر مرجع صريح a.i وa.m1()نعم، إليه وإلى الثابت
thisلا وجود له: لا كائن مستقبِليشير إلى الكائن المستقبِل
UMLمسطَّربلا تسطير
الأثرتغييره يظهر عند كل الكائنات (عداد من 0 إلى 2)تغييره لا يمس كائناً آخر (c1 = 9 وc2 = 5)
أمثلةnumberOfObjects، Math.pow، MAX_RADIUSradius، getArea()

6 المساواة المرجعية والمنطقية == vs equals()

جدول مقارنة: العامل، ==، equals()
العامل==equals()
ماذا يقارنقيمة النوع الأولي، وعنوان المرجع للكائنما تعرّفه إعادة التعريف من معنى للمساواة
السلوك الافتراضيمقارنة هوية: هل هما الكائن نفسهنسخة Object تقارن العناوين أيضاً، فلا فرق بلا إعادة تعريف
متى تستخدمنفس الكائن؟ أو فحص null، وقيم الأعدادتطابق الحروف في String أو القيمة في الغلاف
خطأ شائعمقارنة نصين بـ== فيفشل رغم تطابق الحروفافتراض أنها تقارن المحتوى دائماً بلا إعادة تعريف
الأثرمرجعان إلى الكائن نفسه يعطيان trueكائنان مختلفان بقيم متساوية يعطيان true بعد التعريف

7 التوسيع والتضييق Upcasting / Downcasting

جدول مقارنة: الاتجاه، الصيغة والقسر، ما يظهر والخطر
الاتجاهالصيغة والقسرما يظهر والخطر
توسيع upcastingAnimal a = new Cat(); — إسناد مباشر بلا قسرالدوال المعاد تعريفها تُنفَّذ بنسخة الكائن الفعلي
تضييق downcastingStudent s = (Student) per; — القسر واجبتظهر دوال الابن، وClassCastException إن لم يكن الكائن من النوع
مرجع الأبيرى أعضاء الأب وأسلافه فقطsomeShape.draw() خطأ ترجمة إن كانت draw في Star وحدها
الفحص قبل القسرif (o instanceof Edible) ((Edible) o).howToEat();الفحص يقبل الصنف وابنَه والواجهة المنفَّذة
🎯
القاعدة: الصنف يعرف أباه لا أبناءه؛ والحل الدائم لنداء دالة الابن عبر مرجع الأب هو رفعها مجردة إلى الأب.

8 اثنتا عشرة قاعدة ذهبية Golden Rules

تُحفظ حرفياً

  1. الصنف قالب يحمل التعريف، والكائن يحمل القيم؛ تعديل كائن لا يمس غيره.
  2. الباني اسمه اسم الصنف وبلا نوع إرجاع، ويُستدعى بـnew؛ والافتراضي يُدرج فقط إن لم يُكتب أي باني.
  3. العضو أو الصنف بلا معدّل هو package-private لا public.
  4. private حدّه الصنف لا الكائن؛ والفرع خارج الحزمة يرى public وprotected فقط.
  5. static ملك الصنف ونسخته واحدة؛ لا يصل إلى المثيلي مباشرةً، وthis غير موجود فيه.
  6. تمرير الكائن يمرّر مرجعه: تعديل الحقول يُرى عند المستدعي، وتبديل المراجع لا يُرى.
  7. بواني الأب لا تُورَّث؛ super(...) أول جملة، وthis() وsuper() لا يجتمعان، والبناء يصعد إلى الجذر قبل جسم الابن.
  8. وراثة الأصناف واحدة بـextends، والواجهات متعددة بـimplements.
  9. إعادة التعريف: توقيع مطابق ووراثة وقرار وقت التشغيل؛ التحميل الزائد: وسطاء مختلفة وقرار وقت الترجمة.
  10. الدالة static تُخفى لا تُستبدل، والدالة private لا تُعاد تعريفاً، والوصول يُقوّى ولا يُضعف.
  11. دالة مجردة واحدة تُلزم الصنف abstract، وكل ابن ملموس ينفّذ ما ورثه مجرداً أو يُعلَن مجرداً.
  12. مرجع الأب يقبل كائن ابن بلا قسر ويعمل بنسخة الكائن، ولا يرى دوال الابن إلا بقسر قد يرمي ClassCastException.

9 أخطاء تُفقدك الدرجة Costly Mistakes

في المعدّلات والوصول Modifiers

  • كتابة void للباني أو توقع قيمة إرجاع منه.
  • توقع default constructor بعد كتابة Circle(double).
  • قراءة حقل مثيلي من main بلا كائن، أو استخدام this في static.
  • ظن أن c1.numberOfObjects نسخة مستقلة عن c2.
  • قراءة حقل private من صنف آخر، أو نداء دالة default من حزمة أخرى.
  • setter يسند أي قيمة، فيُلغى نفع التغليف.

في الوراثة والتعدد Inheritance

  • ترك Circle() فارغاً والأب بلا باني بلا وسطاء، أو كتابة جملة قبل super(...).
  • توقع طباعة جسم الابن قبل الأب في سلسلة البناء.
  • معاملة p(int) كإعادة تعريف لـp(double).
  • وضع @Override على دالة private أو static، أو إضعاف protected إلى private.
  • انتظار I am Wolf, I can move مع أن move لم تُعد تعريفها؛ الناتج نسخة الأب.
  • new على صنف مجرد أو واجهة، أو قسر بلا instanceof.

10 تسلسل تنفيذ البواني Constructor Chaining

Faculty.java
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().

الترتيب حرفياً Order

  1. البدء من main ثم new Faculty().
  2. دخول Faculty() وفي أوله super() نحو Employee().
  3. Employee() يبدأ بـthis(String) لا بـsuper.
  4. Employee(String) ينادي Person() ضمناً.
  5. طباعة (1) ثم الرجوع.
  6. طباعة (2) ثم (3) ثم (4).

لا يعمل جسم الباني قبل اكتمال بناء الأب، وthis(...) يؤخّر نداء الأب إلى الباني الآخر.

06

Mock Exam · 40 questions · 45 min

اختبار تجريبي

Practice Quiz — OOP in Java

40 سؤالاً موزّعاً على الفصول الأربعة: 24 اختيار من متعدد، و10 صح/خطأ، و6 «ما ناتج الكود؟». التصحيح فوري، وكل سؤال مرفق بتفسير يشرح الصواب وخطأ كل مشتّت.

📝
طريقة الاستخدام: اختر خياراً واحداً لكل سؤال فتظهر الإجابة الصحيحة والتفسير مباشرة، ويسجّل الشريط أعلى القسم نتيجتك. في أسئلة الصح/الخطأ يكون A) صح وB) خطأ ثابتين في كل الأسئلة. لا تُحفظ الدرجة: «إعادة» تصفّر المحاولة، و«إظهار كل التفسيرات» تكشفها للقراءة السريعة. التوزيع: الفصل 1 (8) · الفصل 2 (9) · الفصل 3 (12) · الفصل 4 (8) · مفاهيم متقاطعة (3).
صحيحة: 0 من 0 مُجابة

1) أين تُحفظ قيمة نصف القطر الخاصة بكائن دائرة بعينه؟

الفصل 1 · الكائن والصنف. الصنف blueprint يعرّف أن لكل دائرة حقلاً باسم radius من النوع double، أما القيمة فحالة تخص الكائن. (a) يخلط القالب بالقيمة. (b) الباني لا يخزّن شيئاً في نفسه، بل يمرّر القيمة إلى حقل الكائن عند new. (c) صنف main يملك متغيرات مرجع إلى الكائنات لا حالتها. الدليل العملي: circle2.setRadius(100) لا يغيّر circle1.

2) أي عبارة تصف الفرق الحاسم بين نموذج EER والنموذج الكائني OO؟

الفصل 1 · نمذجة EER مقابل OO. في EER تُعرَّف الكيانات وحقولها، وتبقى الدوال (Function 1, 2, 3) منفصلة خارج الكيان، ووراثة الكيانات تنقل الخصائص فقط بلا سلوك. في OO يجمع الصنف الحالة والسلوك معاً ويورّث الاثنين. (a) عكس الاتجاه الصحيح. (b) خطأ لأن EER لا يمثّل السلوك أساساً. (d) خطأ لأن EER نمط نمذجة لقاعدة علائقية، ولا يضيف سلوكاً إلى الجداول.

3) ما الفرق الصحيح بين التجريد abstraction والتغليف encapsulation؟

الفصل 1 · الأعمدة الأربعة. التجريد تبسيط واعٍ: نُظهر ما يلزم للوظيفة (أزرار الريموت) ونخفي الدارات، والتغليف يُجمِع الخصائص والسلوك في وحدة واحدة (الصنف) ثم يقيّد الوصول إلى الداخل بحقول private ودوال public. (b) عكس الدورين. (c) خطأ: تغليف جيد قد يقترن بتجريد ضعيف، والعكس صحيح. (d) خطأ: الكلمة المفتاحية abstract أداة تقنية لتحقيق التجريد لا تعريفه، والتغليف لا علاقة له بالوراثة.

4) متى يولّد المترجم الباني الافتراضي default constructor؟

الفصل 1 · البواني. الباني الافتراضي عام وبلا وسطاء وجسمه فارغ، ويُضاف فقط عند غياب أي باني مكتوب. لذلك صنف Circle الذي كتب بانيَين Circle() وCircle(double) لا يملك بانياً ثالثاً ضمنياً. (a) خطأ: وجود أي باني يلغي الإضافة. (c) و(d) شرطان مخترعان: الصنف المجرد قد يملك بواني تُستدعى من الأبناء، والحقول الخاصة لا علاقة لها بالأمر.

5) حقل z معرّف بلا أي معدّل وصول داخل صنف عام C1 في الحزمة p1. أي كود يستطيع قراءة o.z؟

الفصل 1 · محددات الوصول. العضو بلا معدّل package-private: يراه الصنف نفسه وكل أصناف الحزمة نفسها، ولا يراه أحد خارجها. (a) خطأ لأن public على الصنف لا ينقل أعضاءه إلى public. (b) خطأ: هذا سلوك protected، والابن في حزمة أخرى لا يرى الافتراضي. (d) خطأ لأنه يضيّق أكثر من اللازم؛ الأصناف الشقيقة في p1 تراه.

6) صح أم خطأ: عند استدعاء makeNoise() عبر مرجع Animal يُنفَّذ صوت Dog أو Cat حسب نوع الكائن الفعلي، وهذا هو تعدد الأشكال.

الفصل 1 · تعدد الأشكال. العبارة صحيحة: الوراثة تنقل السلوك المشترك، وتعدد الأشكال يجعل الدالة نفسها شكلاً مختلفاً عند كل ابن، ويُختار التنفيذ وقت التشغيل حسب نوع الكائن (method overriding) لا حسب نوع المرجع. لو كان الاختيار بنوع المرجع لطُبع دائماً سلوك Animal العام، وهو ما لا يحدث.

7) صح أم خطأ: ملف Circle.java الذي لا يحوي main يمكن تشغيله مباشرة بالأمر java Circle.

الفصل 1 · الصنف الرئيسي. العبارة خاطئة. Circle تعريف صنف لا برنامج: يُترجَم إلى Circle.class بنجاح، لكن التشغيل يتطلب صنفاً يحوي main (main class) مثل TestCircle، فيُطلق الأمر java TestCircle وتفشل محاولة تشغيل Circle برسالة عدم وجود دالة main.

8) ما ناتج تنفيذ البرنامج التالي؟

Circle.java
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());
    }
}
الفصل 1 · استقلال الكائنات. c1 بُني بالباني بلا وسطاء فنصف قطره 1.0، وc2 بُني بـ25 ثم غيّره setRadius(100) إلى 100.0. (a) يعامل radius كأنه static وهو حقلاً مثيلياً. (b) خطأ: setRadius دالة عادية تعدّل حقل الكائن متى نُوديت. (c) خطأ: الباني ذو الوسيط نُفِّذ فعلاً عند new Circle(25). الكائنان مستقلان، وتغيير أحدهما لا يمسّ الآخر.

9) صنف فيه double radius; وstatic int numberOfObjects = 0;، وأنشأنا منه كائنين. أي وصف صحيح لدور الحقل الثابت؟

الفصل 2 · الأعضاء الثابتة. static تربط العضو بالصنف لا بالمثيل: موضع واحد يشير إليه كل الكائنات، فزيادة العداد من أي باني تظهر عند الجميع. والقراءة باسم الصنف أوضح لأن القيمة ليست ملك كائن. (a) خطأ: هذا وصف الحقل المثيلي، وc1.numberOfObjects وc2.numberOfObjects قراءتان للخانة الواحدة نفسها، لا خانتان. (c) خطأ: الحقل الثابت يُهيَّأ مع تحميل الصنف قبل أي كائن، ومكانه مع بيانات الصنف لا داخل الكائن. (d) خطأ: خلط بين static وfinal؛ الثبات من الأخيرة.

10) لماذا يفشل السطر int j = i; داخل main والصنف فيه int i = 5; مثيلي، بينما ينجح داخل دالة مثيلية؟

الفصل 2 · قيود العضو الثابت. الدالة المثيلية تصل إلى كل شيء لأن معها كائناً (this)، أما الثابتة فلا كائن مستقبِل لها، فلا تصل إلى عضو مثيلي إلا عبر مرجع صريح مثل a.i أو a.m1() بعد new A(). (b) خطأ لأن main تقرأ الثوابت مثل k وتستدعي m2 بلا مشكلة. (c) خطأ: final لا علاقة لها بسياق الاستدعاء. (d) خطأ: المشكلة ليست ترتيب التهيئة بل غياب الكائن الذي يحمل الحقل.

11) الصنف C فيه حقل private boolean x. أي استخدام قانوني له؟

الفصل 2 · محددات الوصول. private حدّه الصنف لا الكائن: كود داخل C يقرأ x حتى عبر c2.x لكائن آخر من النوع نفسه، بينما صف Test خارج C يفشل ترجمته. (a) خطأ: المنع على الخارج فقط. (b) خطأ: الحزمة لا تتجاوز الخاص. (d) خطأ: الخاص لا يورَّث ظهوراً؛ الابن يصل إلى الحالة عبر دوال عامة مثل setX.

12) الحقل خاص وsetRadius فيه radius = (r >= 0) ? r : 0;، ثم نُودي setRadius(-3). ما النتيجة وما قيمتها التصميمية؟

الفصل 2 · تغليف الحقول. المعامل الثلاثي يستبدل السالب بـ0، فيبقى نصف القطر في نطاق صحيح. القيمة التصميمية أن الفحص في مكان واحد داخل setter: لو كان الحقل public لكتب أي عميل myCircle.radius = -3 وألغى الحماية. (a) خطأ: الإسناد يحدث بالقيمة 0 لا يُرفض. (b) يصف السلوك قبل التغليف. (c) خطأ: double يقبل السالب، والاستثناء لم يُكتب في الشفرة.

13) يبدأ circle1.radius = 1 وcircle2.radius = 2. الدالة swap1(x, y) تبدّل الوسيطين المرجعيين محلياً، والدالة swap2(x, y) تبدّل x.radius وy.radius. ماذا يُطبع بعد كل نداء بالترتيب؟

الفصل 2 · تمرير الكائنات. التمرير pass-by-value: المرجع يُنسخ، والكائن على الـheap واحد. swap1 يبدّل نسختين محليتين فيعود كل شيء كما كان، وswap2 يعدّل حقول الكائنين نفسهما فيظهر التبديل. (b) يتوقع أن تبديل المراجع يعود إلى main. (c) يتجاهل أن تعديل الحقول يعود. (d) خطأ: الكائنات تُمرَّر بمراجعها، والتبديل المحلي مشروع.

14) باني Circle(double radius) مكتوب فيه radius = radius; بلا this. ما قيمة الحقل بعد new Circle(5)؟

الفصل 2 · المرجع this. اسم الوسيط يحجب اسم الحقل، فالاسم المجرد radius يعني الوسيط، وradius = radius إسناد للذات لا يغيّر الحقل. الحقل بلا قيمة ابتدائية في الإعلان فيبقى 0.0، والتصحيح this.radius = radius. (a) يفترض حجباً معاكساً. (c) خطأ: الحجب مسموح في Java والترجمة تنجح. (d) خطأ: 1.0 تكون للحقل لو أُعلن بقيمة ابتدائية أو ضبطه الباني الافتراضي.

15) صح أم خطأ: الحقل static موضع واحد في الذاكرة، فتعديله من كائن واحد يظهر عند كل كائنات الصنف.

الفصل 2 · الأعضاء الثابتة. صحيحة. العداد numberOfObjects في مثال الدوائر خانة واحدة: بعد بناء كائنين يُقرأ 2 من أي مرجع ومن اسم الصنف، بينما radius المثيلي يبقى مستقلاً لكل كائن. والخطأ الشائع هو افتراض نسخة لكل كائن، كما لو أن c1.numberOfObjects وc2.numberOfObjects خانتان مختلفتان.

16) صح أم خطأ: تمرير كائن إلى دالة يمرّر نسخة كاملة من الكائن، فتعديل حقوله داخل الدالة لا يظهر عند المستدعي.

الفصل 2 · تمرير الكائنات. خاطئة. الممرَّر هو نسخة من المرجع لا من الكائن: النسختان تشيران إلى الكائن نفسه على الـheap، فأي تعديل حقل عبر المعامل يظهر عند المستدعي كما في swap2. وما لا يعود هو إعادة إسناد المعامل نفسه (x = y)، أو تعديل متغير محلي مثل times؛ وهذا بالضبط سبب فشل swap1 وحده.

17) ما ناتج تنفيذ البرنامج التالي؟

Counter.java
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);
    }
}
الفصل 2 · مثيلي مقابل ثابت. c1.radius صار 9.0 بعد الإسناد المباشر (والحقل بلا معدّل فهو مرئي داخل الحزمة نفسها)، وc2.radius بقي 5.0، والعداد static زاد في البانيَين فصار 2. (a) يعامل radius كأنه مشترك. (b) خطأ: البانيان يزيدان العداد نفسه. (d) يتجاهل إسناد c1.radius = 9.

18) الأب Shape فيه باني واحد Shape(String, boolean) فقط. أي باني ابن يُترجم بنجاح؟

الفصل 3 · مرجع super. الصحيح (b): استدعاء صريح مطابق يحلّ محل super() الضمني. (a) و(d) يعتمدان على باني أب بلا وسطاء غير موجود، فيفشل الترجمة. (c) خطأ لأن super(...) يجب أن يكون أول جملة في الباني، فلا يجوز أن تسبقه أي جملة ولو كانت إسناد حقل.

19) الأب 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);

الفصل 3 · التحميل الزائد مقابل إعادة التعريف. p(int) توقيع مختلف، فهو overloading لا إعادة تعريف، ونسخة الأب p(double) تبقى. الوسيط 10 يطابق p(int) تماماً فيُطبع 10، والوسيط 10.0 لا يطابق إلا p(double) فيُطبع 20.0. (b) هو ناتج حالة إعادة التعريف p(double) في الابن. (c) يفترض أن كلا الاستدعاءين يذهب للأب. (d) يعكس ترتيب الحسم.

20) P فيه static void m() تطبع "P"، وC extends P تعرّف static void m() تطبع "C". ثم P ref = new C(); ref.m();

الفصل 3 · إخفاء الدوال الساكنة. الدالة الساكنة لا تُعاد تعريفها بل تُخفى (hiding): النسختان موجودتان، والربط ساكن فيُحسم وقت الترجمة بنوع المرجع P فيُطبع P. (a) يطبّق قاعدة إعادة التعريف والربط الديناميكي على حالة ساكنة. (b) الشفرة تُترجم بلا خطأ؛ الإخفاء سلوك مشروع. (c) خطأ: إسناد كائن ابن إلى مرجع أب توسيع بلا قسر.

21) C1 في الحزمة p1 فيه x عام وy محمي وz بلا معدّل وu خاص. أي أعضاء تراها C4 extends C1 وهي في الحزمة p2؟

الفصل 3 · جدول الوصول. الابن في حزمة أخرى يرى public وprotected فقط، فيصل إلى x وy. (a) خطأ: private لا يعبر حدّ الصنف حتى إلى الابن. (c) خطأ: protected مصمّم ليصل إلى الأبناء خارج الحزمة. (d) خطأ: z افتراضي، ونطاقه الحزمة التي عُرّف فيها الأب أي p1، لا حزمة الابن.

22) أي عبارة صحيحة عن الجمل Person p = new Student(); وStudent s2 = (Student) p;؟

الفصل 3 · التوسيع والتضييق. إسناد كائن ابن إلى مرجع جدّ هو widening / upcasting ويُكتب بلا قسر لأن كل طالب شخص. وإرجاع المرجع إلى نوع الابن narrowing ويحتاج قسراً، لأنه ليس كل شخص طالباً. (a) و(d) يعمّمان القسر على الاتجاهين. (b) لا معنى له: القسر يتعلق بأنواع الطرفين لا باسم المستقبِل.

23) أي عبارة عن final صحيحة؟

الفصل 3 · المعدّل final. الصحيح (d): final على المتغير تمنع إعادة الإسناد، وعلى الصنف تمنع التمديد مثل final class Math، وعلى الدالة تمنع إعادة التعريف في الابن. (a) تخلط final بـabstract؛ منع الإنشاء وظيفة الثاني. (b) التحميل الزائد توقيع مختلف ولا يمسّه final. (c) الجمع بينهما تناقض يرفضه المترجم.

24) صح أم خطأ: super(...) يجب أن يكون أول جملة في باني الابن، ولا يجتمع مع this(...) في الباني نفسه.

الفصل 3 · مرجع super. صحيحة. جسم باني الابن لا يعمل قبل اكتمال بناء الأب، فنداء الأب أولاً إلزاماً. ولو بدأ الباني بـthis(...) انتقل إلى باني آخر في الصنف نفسه، فيؤجَّل نداء الأب إلى ذلك الباني، ولا يصح الجمع بين النداءين. ومتى غاب الاثنان وُضع super() ضمنياً بشرط وجود باني أب بلا وسطاء.

25) صح أم خطأ: بواني الأب تُورَّث إلى الابن، فيستطيع الابن استدعاء Shape(String, boolean) كأنها دالته.

الفصل 3 · الأب والابن. خاطئة. البواني لا تُورَّث: الابن يرث الحقول والدوال لا البواني، ويستدعي باني الأب بـsuper(...) صريحاً أو ضمنياً، ولهذا يعيد الابن كتابة بوانيه بدل استعارتها.

26) صح أم خطأ: الابن الذي يعيد تعريف public void print() يستطيع تضييق وصولها إلى private.

الفصل 3 · محددات الوصول مع إعادة التعريف. خاطئة. يجوز تقوية الوصول لا إضعافه: protected في الأب تصير public في الابن، أما جعلها private فيفشل الترجمة، لأن كل من يستدعي الدالة عبر مرجع الأب سيفقد الوصول إليها. وإذا كانت دالة الأب private أصلاً فهي غير مرئية، وما يكتبه الابن بالاسم نفسه دالة جديدة لا إعادة تعريف.

27) صح أم خطأ: الصنف الذي لا يكتب extends أبوه java.lang.Object، ولهذا تُورَّث toString() ولو لم تُكتب.

الفصل 3 · Object و toString. صحيحة. كل صنف سليل Object، فالتنفيذ الافتراضي لـtoString متاح دائماً ويُرجع اسم الصنف ثم @ ثم عدداً يمثل الكائن مثل Loan@15037e5، وprintln(loan) تستدعيه ضمنياً. ولهذا يعيد الصنفان Shape وCircle تعريفه ليعطيا نصاً مفهوماً.

28) ما ناتج تنفيذ البرنامج التالي؟

Faculty.java
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(); }
}
الفصل 3 · سلسلة البناء. new Faculty() يدخل Faculty() فيستدعي super() نحو Employee()، الذي يبدأ بـthis("(2)") فينتقل إلى Employee(String)، وهذا يستدعي Person() ضمنياً فيُطبع (1) أولاً، ثم يعود فيُطبع الوسيط (2)، ثم (3) من باني Employee()، ثم (4). (a) تظن أن الوسيط يُطبع قبل بناء الأب. (b) تعكس ترتيب this. (c) تبدأ بالابن، وجسم الابن لا يعمل قبل اكتمال الأب.

29) ما ناتج تنفيذ البرنامج التالي؟

Q29.java
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();
    }
}
الفصل 3 · المصفوفة متعددة الأشكال. نوع الخانات Animal والكائنات من أبناء، فالاختيار وقت التشغيل لنوع الكائن. Cat أعادت تعريف eat فتُطبع نسختها، وWolf لم تعد تعريف move فتبقى نسخة الأب. (a) تتوقع تعريفاً غير موجود في Wolf. (b) تتجاهل إعادة التعريف في Cat. (d) خطأ: الإسناد قانوني، وليس مطلوباً من الابن تعريف كل دوال الأب، بل الدوال المجردة وحدها.

30) هل يجوز abstract class Student بلا أي دالة مجردة، وما الفائدة؟

الفصل 4 · قواعد التجريد. القاعدة تسير في اتجاه واحد: وجود دالة مجردة يُلزم صنفاً مجرداً، ولا يلزم العكس. Student مجرد ليمنع إنشاء «طالب عام» ويُبقي setLogin وgetLogin مشتركة بين PhdStudent وغيره. (a) تقلب الاتجاه. (c) خطأ: الواجهة بلا بنّاء وبلا حقول كائن ولا تنفيذ، والصنف المجرد يحمل حالة وتنفيذاً وهو أب واحد. (d) شرط مخترع لا أساس له.

31) Shape صنف مجرد، وgeo من نوع Shape[]. أي العبارتين قانونية: Shape[] geo = new Shape[10]; أم geo[0] = new Shape();؟

الفصل 4 · الصنف المجرد نوع بيانات. الممنوع هو new على النوع المجرد لا استخدامه نوعاً. إنشاء المصفوفة يُهيّئ عشر خانات null بلا أي كائن، ويُملأ كل عنصر بابن ملموس مثل new Circle(5). (a) خطأ: المصفوفة لا تُنشئ كائنات. (b) معكوسة. (c) خطأ: النوع المجرد والواجهة نوعان مرجعيان صالحان تماماً، وهذا أساس polymorphism.

32) داخل public interface T كُتب int K = 1; بلا معدّلات. ما وضع هذا الحقل؟

الفصل 4 · جسم الواجهة. كل حقل في الواجهة public static final حتى لو حُذفت المعدّلات، وكل دالة public abstract. ولأن الحقل ثابت فلا بد أن يُهيَّأ في إعلانه. (a) خطأ: الواجهة لا تحمل حالة كائن. (b) خطأ: لا وجود لوصول افتراضي داخلها، والوضع المفروض هو العام. (d) خطأ: الثبات مفروض، والتعديل ممنوع.

33) class Star extends Shape { public void draw() { } } ثم Shape someShape = new Star(); someShape.draw(); — ما الحكم وما العلاج؟

الفصل 4 · ما يراه نوع المرجع. الصنف يعرف أباه لا أبناءه، والمترجم ينظر إلى نوع المرجع Shape فلا يجد draw. العلاج المباشر تحويل قسري قبل الاستدعاء (وقد يرمي ClassCastException إن لم يكن الكائن Star)، والعلاج الأفضل رفع draw إلى Shape مجردة فيصير الاستدعاء قانونياً بلا تحويل. (a) تتجاهل فحص الترجمة. (c) الخطأ ترجمة لا وقت تشغيل. (d) @Override لا تضيف عضواً إلى الأب.

34) أي جملة تصف قاعدة الاختيار بين الوراثة extends والتنفيذ implements؟

الفصل 4 · متى الواجهة ومتى الوراثة. Staff وPerson علاقة is-a قوية تشارك حقولاً وشيفرة فتُنمذج بوراثة صنف. Fruit وEdible علاقة is-kind-of ضعيفة تشارك صفة فتُنمذج بواجهة، ولولاها لاستهلكت الدجاجة مقعد extends الوحيد. (b) عكس الدورين. (c) عكس الكلمتين. (d) صحيح أنهما يمنعان new ويصلحان نوعاً، لكنهما يختلفان في المحتوى وعدد الآباء.

35) صح أم خطأ: الواجهة تحوي ثوابت مُهيّأة فقط ولا بنّاء، ويجوز لصنف واحد تنفيذ عدة واجهات مع الامتداد من صنف واحد.

الفصل 4 · الواجهة. صحيحة. جسم الواجهة ثوابت public static final وتواقيع دوال public abstract، بلا حقول كائن وبلا بنّاء، ولهذا لا new عليه. والصيغة class NewClass extends BaseClass implements Interface1, Interface2 هي الالتفاف على قيد الوراثة الأحادية: extends لصنف واحد، وimplements لعدد غير محدود. أما الواجهة نفسها فتمتد من عدة واجهات وتُسمّى subinterface.

36) ما ناتج تنفيذ البرنامج التالي؟

Test.java
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());
        }
    }
}
الفصل 4 · الواجهة و instanceof. Tiger ليس Edible فيفشل الفحص ويُتخطى بلا استثناء، وChicken وApple ينفّذان howToEat فيُطبع سطراهما بالترتيب. (a) خطأ: الشرط يستبعد النمر. (b) خطأ: Fruit مجرد لأنه ترك دالة الواجهة، وهذا قانوني، والابن الملموس Apple نفّذها. (d) خطأ: التحويل محميّ بـinstanceof، فما لا يُفحص لا يُحوَّل. وObject لا يعرف howToEat، ولهذا يلزم التحويل بعد نجاح الفحص.

37) ما ناتج تنفيذ البرنامج التالي؟

Test.java
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());
    }
}
الفصل 4 · الدالة المجردة والربط الديناميكي. Shape s مرجع من نوع مجرد يقبل كائن ابن ملموس، والاستدعاء يُحسم وقت التشغيل فيُنفَّذ Rect.getArea فيُطبع 6.0. (a) تخلط المساحة بالمحيط. (c) خطأ: النوع المجرد صالح للمراجع والمصفوفات، والممنوع new عليه فقط. (d) خطأ: الدالة المجردة عقد بلا جسم في الأب، والابن الملموس ملزم بتقديم الجسم، ولو تركه لوجب إعلانه مجرداً.

38) أي مقطع من المقاطع التالية يسبب خطأ ترجمة؟

مفاهيم متقاطعة · الوصول والواجهات والتجريد. (d) هو الخطأ: إعادة التعريف يجوز أن تقوّي الوصول لا أن تضيّقه، وإلا انكسر كل استدعاء عبر مرجع الأب. (a) مشروع لأن الدالة المثيلية تصل إلى الأعضاء الثابتة. (b) مشروع لأن حقل الواجهة public static final مفروض ويُهيَّأ في إعلانه. (c) مشروع: باني protected هو العُرف في الأصناف المجردة لأن الأبناء وحدهم يستدعونه بـsuper.

39) أي مقطع يترجم ويُنفّذ سلوك Dog عبر مرجع من نوع الأب؟

مفاهيم متقاطعة · التجريد وتعدد الأشكال. (a) يجمع القاعدتين: رفع sound مجردة إلى Animal يجعل الاستدعاء قانونياً عبر المرجع، والربط الديناميكي ينفّذ نسخة Dog. (b) خطأ ترجمة لأن Animal مجرد. (c) إسناد كائن أب إلى مرجع ابن: غير متوافق بلا قسر، ولن يمرّ إلا بقسر يفشل وقت التشغيل. (d) bark ليست في Animal، والتحويل إلى Animal لا يضيف عضواً.

40) صح أم خطأ: Animal a = new Dog(); توسيع بلا قسر، وDog d = (Dog) a; تضييق يلزمه القسر وقد يرمي ClassCastException إن لم يكن الكائن Dog فعلاً.

مفاهيم متقاطعة · المراجع والتحويل. صحيحة. الإسناد إلى مرجع الجدّ widening تلقائي لأن كل كلب حيوان، والإرجاع إلى الابن narrowing يحتاج قسراً لأن ليس كل حيوان كلباً؛ فيُقبل عند الترجمة إذا كان النوعان متصلين، وقد يفشل وقت التشغيل إن كان الكائن الفعلي قطاً. ولهذا يُسبَق التحويل عادة بفحص instanceof.

كيف تقرأ نتيجتك

  • 36–40: جاهز للاختبار. راجع فقط الأسئلة التي أخطأت فيها وأعد قراءة قسمها المرتبط، فالخطأ الواحد عادةً فخّ واحد لا فجوة معرفية.
  • 30–35: الأساس متين. ثغراتك غالباً في التتبع لا في المفاهيم: أعد تنفيذ أمثلة Circle وCircleWithStaticMembers وFaculty وTestEdible يدوياً على الورق.
  • 22–29: راجع جدول الوصول وجدول overriding vs overloading وترتيب سلسلة البناء؛ هذه أكثر ثلاث نقاط تُفقد فيها الدرجات في هذا المقرر.
  • أقل من 22: ابدأ من ملخصات الفصول لا من الاختبار: الأعمدة الأربعة، مثيلي/ثابت، super وthis، قواعد التجريد والواجهات، ثم أعد المحاولة.
  • في كل سؤال أخطأت فيه، اسأل نفسك: هل الخطأ في المفهوم أم في التتبع؟ فالأول يُعالج بقراءة القسم، والثاني بكتابة الكود وتشغيله فعلياً.
  • الدرجة هنا ليست علامة نهائية: «إعادة» تصفّر المحاولة كلها، فراجع التفسير بعد كل خطأ بدل المرور السريع.