1
تمرير الكائنات إلى الدوال
لنبدأ باستكمال رحلتنا في عالم البرمجة كائنية التوجه. في الدروس السابقة تعلّمنا كيف نعرّف الكلاس، وننشئ الكائنات، ونكتب المُنشئات. والآن، ماذا لو أردنا تمرير كائن كمعامل إلى دالة؟ الأمر بسيط بشكل مدهش — وفي غاية القوة.
فكّر في الأمر هكذا: تمامًا كما يمكنك تمرير int أو double إلى دالة، يمكنك تمرير كائن كامل. وستعمل الدالة مع ذلك الكائن عن طريق مرجع (reference).
Circle.java
public class Circle {
private double radius;
private String color;
public Circle(double radius, String color) {
this.radius = radius;
this.color = color;
}
public double getArea() {
return Math.PI * radius * radius;
}
public String toString() {
return "Circle[radius=" + radius + ", color=" + color + "]";
}
}
Main.java — تمرير كائن Circle إلى دالة
public class Main {
public static void printCircleInfo(Circle c) {
System.out.println("Circle info : " + c);
System.out.println("Area : " + c.getArea());
}
public static void main(String[] args) {
Circle myCircle = new Circle(5.0, "red");
printCircleInfo(myCircle);
}
}
💡 مفهوم أساسي — التمرير بقيمة المرجع
في جافا، عندما تمرر كائنًا إلى دالة، فأنت في الحقيقة تمرّر
المرجع (عنوان الكائن في الذاكرة). كلٌّ من المُستدعي والدالة ينظران إلى
نفس الكائن في الذاكرة. أي تعديلات تُجرى على حقول الكائن
داخل الدالة ستكون مرئية
خارجها أيضًا.
📌 لماذا هذا مفيد؟
- يمكنك كتابة دوال مساعدة تعمل على أي كائن من كلاس معيّن.
- يمكنك مقارنة كائنين داخل دالة (مثلاً:
compareAreas(Circle c1, Circle c2)).
- يمكن للدوال تعديل حالة الكائن الممرَّر — مما يمنحك مرونة هائلة.
2
الحالة (instance) مقابل static — القصة الكاملة
2.1 — ماذا يعني مصطلح «الحالة (instance)»؟
لنبدأ بشيء تعرفه بالفعل. لقد كتبت من قبل كودًا مثل هذا:
Circle c1 = new Circle(5.0, "red");
ربما قلت: «لقد أنشأت كائنًا من كلاس Circle.» هذا صحيح. لكن إليك النقطة المهمة — يمكنك قول الأمر بطريقة مختلفة:
«لقد أنشأت حالةً (instance) من Circle.»
الجملتان تعنيان تمامًا نفس الشيء. كلمة instance تؤكّد فقط أن هذا الكائن بالذات وُلد من كلاس Circle. ويمكننا إنشاء حالات متعددة من نفس الكلاس — كل واحدة مستقلة، ولها بياناتها الخاصة.
📖 التعريف — الحالة (instance)
الحالة (instance) هي كائن محدد وملموس يُنشأ من كلاس. في كل مرة تستخدم فيها الكلمة المفتاحية
new، تُنشئ حالة جديدة. كل حالة تعيش في منطقة خاصة بها في الذاكرة (الـ Heap).
2.2 — متغيرات الحالة (instance) ودوالها
انظر إلى كلاس Circle. يحتوي على radius وcolor. تخيّل الآن أننا أنشأنا دائرتين:
Circle c1 = new Circle(5.0, "red");
Circle c2 = new Circle(10.0, "blue");
تصوير الذاكرة — حالتان مستقلتان
📌 متغير الحالة (Instance Variable)
radius و
color هما
متغيرا حالة لأن كل حالة (c1, c2) لها
حالة خاصة بها من هذه القيم. تغيير نصف قطر c1 لا يؤثر على نصف قطر c2.
📌 دالة الحالة (Instance Method)
دالة مثل
getArea() هي
دالة حالة لأنها تنتمي إلى حالة محددة — إنها تعمل على بيانات
تلك الحالة (instance) (
this.radius). يجب استدعاؤها على كائن:
c1.getArea().
2.3 — المتغيرات الساكنة (Static): مشتركة بين جميع الحالات
والآن السؤال الكبير: ماذا لو أردنا متغيرًا لا يملكه أي كائن بمفرده، بل يكون مشتركًا بين جميع الحالات؟ على سبيل المثال، لنقل إننا نريد حساب كم كائن Circle تم إنشاؤه حتى الآن. أين نضع هذا العدّاد؟
لو جعلنا counter متغير حالة عادية، فكل دائرة ستمتلك عدّادها الخاص — وسيكون دائمًا 1. هذا لا فائدة منه. نحتاج متغيرًا تنظر إليه جميع الدوائر معًا.
هذا بالضبط ما تفعله الكلمة المفتاحية static.
Circle.java — إضافة عدّاد static
public class Circle {
private double radius;
private String color;
private static int counter = 0;
public Circle(double radius, String color) {
this.radius = radius;
this.color = color;
counter++;
}
public static int getCounter() {
return counter;
}
}
Main.java — إنشاء ثلاثة كائنات Circle
Circle c1 = new Circle(5, "Red");
Circle c2 = new Circle(10, "Blue");
Circle c3 = new Circle(7, "Green");
System.out.println(Circle.getCounter());
لقد أنشأنا الآن ثلاثة كائنات من نوع Circle. كل كائن له radius وcolor خاصان به — فهما متغيرا حالة، خاصان بكل كائن. أما counter فهو متغير static: لا ينتمي لأي كائن بعينه. في كل مرة تُنشأ دائرة جديدة، تنظر الثلاثة إلى نفس قيمة العدّاد.
التصوير التالي يُظهر بالضبط كيف يبدو هذا في الذاكرة — كيف أن c1 وc2 وc3 يشيرون جميعهم إلى نفس counter الساكن.
تصوير الذاكرة — static مقابل حالة
كيف يتغير العدّاد مع إنشاء الكائنات:
1
الحالة (instance) الأولى أُنشئت
2
العدّاد مشترك — حالة واحدة فقط
3
جميع الحالات ترى نفس الرقم 3
⭐ القاعدة الذهبية — Static مقابل الحالة (instance)
- متغير/دالة الحالة (instance) — ينتمي لكائن محدد. كل كائن له حالته الخاصة. يُستخدم عبر:
objectName.field
- المتغير/الدالة الساكنة (Static) — ينتمي للـكلاس نفسه. مشترك بين جميع الكائنات. يُستخدم عبر:
ClassName.field (مثل: Circle.getCounter())
مخطط UML — الخط تحت العنصر يعني أنه static:
Circle
– radius : double (instance)
– color : String (instance)
– counter : int (static)
+ getArea() : double (instance)
+ getCounter() : int (static)
3
لماذا نحتاج static؟ الدوافع والأسباب
ربما يرفع أحد الطلاب يده ويقول: «أستاذ، أنا أفهم ما هو static — لكن لماذا نحتاجه؟ أليس بإمكاننا فعل كل شيء بمتغيرات الحالة (instance) العادية؟»
سؤال ممتاز. دعنا نتأمّل سيناريوهين حقيقيين يجعلان static ضرورةً لا غنى عنها.
السيناريو الأول — معرّفات طلاب عشوائية فريدة (لماذا نحتاج متغير static)
🎓 المشكلة
تخيّل أن لدينا كلاس Student بحقول: studentID وname وage.
الشرط هو: يجب أن يحصل كل طالب تلقائيًا على معرّف عشوائي فريد من النطاق 1 إلى 2000. المعرّفات ليست تسلسلية — إنها عشوائية. قد يحصل علي على 5، وهناء على 100، وعمر على 800. لكن لا يجوز لطالبَين أن يشتركا في نفس المعرّف.
فكّر جيدًا — كيف نضمن التفرّد؟ كل حالة مستقلة تمامًا. عند إنشاء كائن علي، لا يعلم بالمعرّفات التي أخذتها حالات هناء وعمر. هذا هو التحدي الحقيقي.
الخطوة ١ — فكرة الطالب الأولى (ولماذا تفشل)
يرفع أحد الطلاب يده ويقول: «أستاذ، يمكن لكلاس Student توليد المعرّف العشوائي بنفسه داخل المُنشئ. لكن سأحتفظ بمصفوفة متابعة في main. بعد إنشاء الطالب، أتحقق من معرّفه — إن كان مأخوذًا، أتجاهل الكائن وأنشئ آخر. أكرر حتى أحصل على معرّف فريد.»
ابتسم الأستاذ. «تفكير جيد — لكن دعنا نرى كيف يبدو ذلك في الكود.»
Student.java — الكلاس يولّد معرّفًا عشوائيًا (بدون ضمان التفرّد)
public class Student {
private String name;
private int age;
private int studentID;
public Student(String name, int age) {
this.name = name;
this.age = age;
this.studentID = new Random().nextInt(2000) + 1;
}
public int getStudentID() { return studentID; }
public String toString() { return "Student[ID=" + studentID + "] " + name; }
}
Main.java — المستدعي يتحقق من التكرار، يتجاهل الكائنات ويعيد الإنشاء (تصميم سيء)
public static void main(String[] args) {
boolean[] usedIDs = new boolean[2001];
Student s1;
do {
s1 = new Student("Ali", 20);
} while (usedIDs[s1.getStudentID()]);
usedIDs[s1.getStudentID()] = true;
Student s2;
do {
s2 = new Student("Hana", 21);
} while (usedIDs[s2.getStudentID()]);
usedIDs[s2.getStudentID()] = true;
Student s3;
do {
s3 = new Student("Omar", 19);
} while (usedIDs[s3.getStudentID()]);
usedIDs[s3.getStudentID()] = true;
}
❌ لماذا هذا الأسلوب خاطئ — يخرق مبدأ التجريد
تخيّل مطوّرًا يريد استخدام كلاس
Student. سيصاب بالجنون. في كل مرة يُنشئ طالبًا، يُجبَر على الاحتفاظ بقائمة، وكتابة حلقة، وإدارة التحقق من المعرّفات بنفسه. هذه ليست مهمته — هذه مهمة الكلاس. التجريد مكسور.
يقول الأستاذ:
«وجدت الفكرة الصحيحة — مصفوفة المتابعة. لكنها تنتمي داخل الكلاس، لا في main. اجعلها static، وسيتولى الكلاس كل شيء بنفسه.»
الخطوة ٢ — الحل الحقيقي: مصفوفة static داخل الكلاس
الإدراك هنا: نحتاج مخزنًا مشتركًا للمعرّفات المستخدمة يمكن لكل حالة من Student الرجوع إليه وتحديثه. يجب أن يكون هذا المخزن مشتركًا — لا مضاعفًا لكل كائن.
نُعلن عن مصفوفة static boolean[] داخل كلاس Student. لأنها static، فهناك حالة واحدة بالضبط بغض النظر عن عدد كائنات Student الموجودة. كل حالة تنظر إلى نفس المصفوفة.
الخوارزمية داخل المُنشئ هي:
📋 خوارزمية توليد المعرّف (تعمل داخل المُنشئ)
1
توليد رقم عشوائي بين 1 و2000.
2
البحث عن هذا الرقم في مصفوفة usedIDs الساكنة. هل usedIDs[randomNumber] مُسجّل بالفعل كـ true؟
3
إذا كان نعم (مستخدم) — عُد إلى الخطوة 1 وولّد رقمًا آخر.
4
إذا كان لا (متاح) — سجّل usedIDs[randomNumber] = true واجعل هذا الرقم معرّف الطالب.
تصوير — مصفوفة usedIDs الساكنة بعد إنشاء ٣ طلاب:
المنطقة الساكنة — حالة واحدة مشتركة، مرئية لجميع حالات Student
■ true = هذا المعرّف مأخوذ بحالة |
■ false = هذا المعرّف لا يزال متاحًا
Student.java — معرّفات عشوائية فريدة ذاتية بمصفوفة static
import java.util.Random;
public class Student {
private String name;
private int age;
private int studentID;
private static boolean[] usedIDs = new boolean[2001];
private static Random rand = new Random();
public Student(String name, int age) {
this.name = name;
this.age = age;
this.studentID = generateUniqueID();
}
private static int generateUniqueID() {
int id;
do {
id = rand.nextInt(2000) + 1;
} while (usedIDs[id]);
usedIDs[id] = true;
return id;
}
public String toString() {
return "Student[ID=" + studentID + "] " + name + ", age=" + age;
}
}
Student s1 = new Student("Ali", 20);
Student s2 = new Student("Hana", 21);
Student s3 = new Student("Omar", 19);
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
💡 لماذا يعمل هذا — المصفوفة الساكنة هي السر
usedIDs هي static — توجد مصفوفة واحدة بالضبط، مشتركة بين كل حالة Student أُنشئت.
- عندما تُسجّل s1 القيمة
usedIDs[5] = true، يظهر هذا التغيير فورًا لـ s2 وs3. ولن يختارا الرقم 5 أبدًا.
- المستدعي يكتب فقط
new Student("Ali", 20). تعقيد المعرّف مخفي تمامًا داخل الكلاس. هذا هو التغليف والتجريد يعملان معًا.
generateUniqueID() هي أيضًا static لأنها تتعامل فقط مع الحقول الساكنة — لا تحتاج أي بيانات من حالة معينة.
السيناريو الثاني — دوال المساعدة (لماذا نحتاج دالة static)
الآن دعنا نفكّر في الدوال الساكنة (static methods). متى يجب أن تكون الدالة static؟
يجب أن تكون الدالة static عندما لا تحتاج الوصول إلى أي متغير حالة. إنها تؤدي عملية عامة لا تعتمد على حالة أي كائن محدد.
🧮 السيناريو — دوال رياضية مساعدة
فكّر في Math.sqrt() وMath.pow() وMath.abs(). استخدمتها طوال الفصل الدراسي. لاحظ أنك لم تكتب new Math() أبدًا. لماذا؟ لأنها دوال static — تعيش على الكلاس نفسه، لا على أي كائن.
مثال آخر حقيقي: لنقل إننا نكتب كلاس Circle ونريد دالة تأخذ قيمتَي نصف قطر وتخبرنا أيهما ينتج مساحة أكبر. هذه الدالة لا تحتاج حالة محددة — إنها مجرد حساب. جعلها static هو الاختيار الطبيعي والصحيح.
Circle.java — دالة static مساعدة
public class Circle {
private double radius;
public Circle(double radius) { this.radius = radius; }
public double getArea() {
return Math.PI * radius * radius;
}
public static double largerArea(double r1, double r2) {
double area1 = Math.PI * r1 * r1;
double area2 = Math.PI * r2 * r2;
return Math.max(area1, area2);
}
}
double bigger = Circle.largerArea(4.0, 7.0);
System.out.println("Larger area: " + bigger);
⭐ متى نستخدم الدوال الساكنة؟
- الدالة لا تستخدم أي متغير حالة (لا يوجد
this.something).
- الدالة تؤدي عملية مساعدة عامة (رياضيات، تحويل، منطق مصنع).
- تريد أن يستخدمها المستدعون بدون إنشاء كائن أولًا.
⚠️ قيد مهم
الدالة الساكنة لا تستطيع الوصول إلى متغيرات الحالة (instance) أو استدعاء دوال الحالة (instance) مباشرةً. لماذا؟ لأنه عند استدعاء دالة static، قد لا يوجد أي كائن على الإطلاق — لذا
this غير موجود داخل السياق الساكن.
4.1 — الصورة الكبيرة: المشروع ← الحزمة ← الكلاس
يُنظَّم برنامج جافا في طبقات. فكّر في الأمر كبناية:
📦
com.university.shapes
حزمة — تجمع الكلاسات المترابطة
📄
Circle.java
كلاس داخل الحزمة
📄
Rectangle.java
كلاس آخر في نفس الحزمة
📦
com.university.students
حزمة مختلفة
📄
Student.java
اهتمام مستقل تمامًا
📦 لماذا نحتاج الحزم؟
الحزم للتنظيم
البحت. مع نموّ مشروعك إلى عشرات أو مئات الكلاسات، يصبح وضع كل شيء في مجلد واحد فوضى. تتيح لك الحزم تجميع الكلاسات المترابطة — مثل المجلدات على جهازك. كما تسمح لحزمتين مختلفتين بامتلاك كلاسات بنفس الاسم دون تعارض.
4.2 — محددات الوصول
السؤال الحاسم الآن: إذا كان مشروعي يحتوي كلاسات عديدة موزّعة على حزم متعددة، من يحق له رؤية واستخدام أي حقول ودوال؟ هذا يتحكم فيه محددات الوصول (Visibility Modifiers).
في جافا أربعة محددات:
🌍
public — مرئي في كل مكان
أي كلاس في أي حزمة في أي مشروع يمكنه الوصول إلى هذا العضو. هذا أعلى مستوى من الانفتاح.
public int radius; — يمكن لأي كود قراءته.
🔒
private — مرئي داخل هذا الكلاس فقط
فقط الكود المكتوب
داخل نفس الكلاس يمكنه الوصول. حتى الكلاس الوارث لا يستطيع رؤيته. هذا أعلى مستوى من الحماية.
private int radius; — فقط دوال Circle تستطيع قراءته.
📦
default (بدون كلمة مفتاحية) — مرئي داخل نفس الحزمة
إذا لم تكتب أي محدد، تستخدم جافا الـ «default» أو «package-private». فقط الكلاسات في
نفس الحزمة تستطيع الوصول. الكلاسات في حزم أخرى لا تستطيع.
int radius; — بدون كلمة مفتاحية يعني default.
🛡️
protected — نفس الحزمة + الكلاسات الوارثة
مثل default، لكنه يمتد أيضًا إلى
الكلاسات الوارثة (الكلاسات الأبناء)، حتى لو كانت في حزمة مختلفة. سنفهم هذا تمامًا بعد دراسة
الوراثة (Inheritance) — لذا سنعود إليه لاحقًا.
protected int radius;
جدول ملخّص — من يمكنه الوصول؟
| المحدد |
نفس الكلاس |
نفس الحزمة |
كلاس وارث (حزمة مختلفة) |
في كل مكان |
| public |
✔ |
✔ |
✔ |
✔ |
| protected |
✔ |
✔ |
✔ |
✗ |
| default |
✔ |
✔ |
✗ |
✗ |
| private |
✔ |
✗ |
✗ |
✗ |
تصوير — الوصول عبر المشروع
🏗️ Project
📦 الحزمة: com.shapes
Circle
private
radius
public
getArea()
default
helper()
Rectangle
✔ يمكن استدعاء getArea() (public)
✔ يمكن استدعاء helper() (default — نفس الحزمة)
✗ لا يمكن الوصول إلى radius (private)
📦 الحزمة: com.students
Student
✔ يمكن استدعاء getArea() (public)
✗ لا يمكن استدعاء helper() (default — حزمة مختلفة)
✗ لا يمكن الوصول إلى radius (private)
⏳ عن protected — يأتي لاحقًا
لكي تفهم
protected كاملًا، تحتاج أن تفهم
الوراثة (Inheritance) (الكلاس الأب والكلاس الابن). سنعود إليه في درس الوراثة. في الوقت الحالي، فقط اعلم بوجوده وأنه يقع بين
default و
public من حيث الانفتاح.
5
التغليف: الحقول الخاصة والجاليات والمُعيِّنات
5.1 — لماذا يجب أن تكون حقول البيانات private؟
كنّا نكتب private أمام حقول البيانات. لكن لماذا؟ ما المشكلة في استخدام public؟
🔓 تخيّل حقولًا public — الخطر
لنقل إنك أعلنت: public double radius;
الآن أي كود في أي مكان في المشروع يمكنه كتابة: myCircle.radius = -999;
نصف قطر سالب لا معنى له. لكن جافا ستسمح بذلك لأنه لا توجد حماية. كائنك الآن في حالة غير صالحة ومعطوبة — ومن المستحيل تطبيق القواعد من داخل الكلاس.
✅ حقول private + وصول متحكَّم = التغليف
عندما يكون الحقل
private، الطريقة الوحيدة لقراءته أو تغييره من الخارج هي الدوال التي تقدّمها أنت عن قصد. داخل تلك الدوال يمكنك
التحقق من أي قيمة قبل قبولها. هذا يحمي سلامة كائنك في جميع الأوقات.
📖 التغليف (Encapsulation)
التغليف هو مبدأ الـ OOP الذي يقضي بتجميع البيانات والدوال التي تعمل عليها داخل كلاس واحد، و
تقييد الوصول المباشر للبيانات. يجعل الكود أسهل للصيانة، ويمنع إساءة الاستخدام، ويسمح بتغيير التنفيذ الداخلي دون التأثير على الكود الخارجي.
5.2 — دوال الوصول (Getters) والتعيين (Setters)
عندما تجعل الحقل private، توفّر وصولًا متحكَّمًا عبر نوعين من الدوال ستسمع عنهما باستمرار في تطوير جافا الاحترافي:
الغرض: السماح بقراءة قيمة حقل private.
اتفاقية التسمية: get + اسم الحقل
تُرجع: قيمة الحقل — بدون تعديل.
مثال: getRadius()، getColor()، getName()
الغرض: السماح بتغيير قيمة حقل private — مع التحقق.
اتفاقية التسمية: set + اسم الحقل
تُرجع: void. تأخذ معاملًا واحدًا.
مثال: setRadius(double r)، setColor(String c)
Circle.java — كلاس مُغلَّف بالكامل
public class Circle {
private double radius;
private String color;
public Circle(double radius, String color) {
setRadius(radius);
this.color = color;
}
public double getRadius() {
return radius;
}
public String getColor() {
return color;
}
public void setRadius(double radius) {
if (radius < 0) {
System.out.println("Error: radius cannot be negative. Keeping default.");
this.radius = 1.0;
} else {
this.radius = radius;
}
}
public void setColor(String color) {
this.color = color;
}
public double getArea() {
return Math.PI * radius * radius;
}
}
Circle c = new Circle(5.0, "red");
System.out.println(c.getRadius());
c.setRadius(-10);
c.setRadius(8.0);
💡 لماذا هذا التصميم قوي
- سلامة البيانات: القيم غير الصالحة تُرفض قبل إفساد الكائن.
- سهولة الصيانة: غيّر تنسيق التخزين الداخلي في أي وقت — الكود الخارجي الذي يستخدم الـ getters/setters لن يتأثر.
- التصحيح سهل: كل قراءة/كتابة تمر من مكان واحد — يمكنك إضافة التتبع فورًا.
حتى الآن خزّنّا الكائنات المفردة في متغيرات مثل Student s1 وStudent s2 وStudent s3. هذا مناسب لكائنَين أو ثلاثة. لكن ماذا لو كان لدينا 100 طالب؟ كتابة 100 متغير مستقل أمر مستحيل.
الحل هو مصفوفة من الكائنات — وتعمل بشكل مشابه جدًا لمصفوفة البدائيات، مع فارق جوهري واحد يجب أن تفهمه جيدًا.
6.1 — إعلان مصفوفة الكائنات وإنشاؤها
إنشاء مصفوفة من 5 كائنات Student
Student[] students = new Student[5];
students[0] = new Student("Ali", 20);
students[1] = new Student("Hana", 21);
students[2] = new Student("Omar", 19);
students[3] = new Student("Lina", 22);
students[4] = new Student("Reem", 20);
System.out.println(students[0].getName());
🔑 مفهوم حاسم — المصفوفة تخزّن المراجع (References)، لا الكائنات
هذه واحدة من أهم النقاط التي يجب فهمها. عند إنشاء new Student[5]، تُنشئ جافا مصفوفة من 5 خانات مرجعية — لا 5 كائنات Student. الكائنات نفسها تعيش بشكل منفصل في ذاكرة الـ Heap.
المصفوفة تحتوي عناوين (مراجع) تشير إلى أماكن الكائنات. يمكن أن تكون الكائنات مبعثرة في أي مكان من الـ Heap.
تصوير الذاكرة — مصفوفة مراجع الكائنات
students[ ]
[0]ref
[1]ref
[2]ref
[3]ref
[4]ref
Student — "Ali"
name: "Ali" | age: 20 | ID: 5
Student — "Hana"
name: "Hana" | age: 21 | ID: 100
Student — "Omar"
name: "Omar" | age: 19 | ID: 800
Student — "Lina"
name: "Lina" | age: 22 | ID: 433
Student — "Reem"
name: "Reem" | age: 20 | ID: 1756
💡 الكائنات ليست مُحزَّمة في المصفوفة
المصفوفة مجرد صف أنيق من
العناوين. كل عنوان هو مرجع يشير إلى حيثما يعيش الكائن المقابل في الـ Heap. قد تكون كائنات Student متباعدة في الذاكرة — المصفوفة لا تنقلها معًا. إنها تحتفظ فقط بالاتجاهات للعثور عليها.
6.2 — التكرار عبر مصفوفة الكائنات
باستخدام حلقة for
for (int i = 0; i < students.length; i++) {
System.out.println(students[i].toString());
}
باستخدام حلقة for-each المحسّنة (أكثر وضوحًا)
for (Student s : students) {
System.out.println(s.getName() + " — Age: " + s.getAge());
}
⚠️ انتبه دائمًا للقيمة null
تذكر: عند إنشاء
new Student[5]، جميع الخانات تحتوي
null افتراضيًا. إذا حاولت استدعاء دالة على خانة
null، ستحصل على
NullPointerException. إذا لم تملأ جميع الخانات، استخدم التحقق من null:
for (Student s : students) {
if (s != null) {
System.out.println(s.getName());
}
}
6.3 — مثال كامل قابل للتشغيل
Main.java — مثال كامل على مصفوفة كائنات
public class Main {
public static void main(String[] args) {
Student[] students = new Student[5];
students[0] = new Student("Ali", 20);
students[1] = new Student("Hana", 21);
students[2] = new Student("Omar", 19);
students[3] = new Student("Lina", 22);
students[4] = new Student("Reem", 20);
System.out.println("=== All Students ===");
for (Student s : students) {
System.out.println(s);
}
Student oldest = students[0];
for (int i = 1; i < students.length; i++) {
if (students[i].getAge() > oldest.getAge()) {
oldest = students[i];
}
}
System.out.println("\nOldest student: " + oldest.getName());
}
}
📋 الناتج
=== All Students ===
Student[1] Ali — Age: 20
Student[2] Hana — Age: 21
Student[3] Omar — Age: 19
Student[4] Lina — Age: 22
Student[5] Reem — Age: 20
Oldest student: Lina
★
الخلاصة — المواضيع الستة
١. تمرير الكائنات
عند تمرير كائن إلى دالة، تُمرَّر قيمة المرجع. تُشير كلٌّ من الدالة والمُستدعي إلى نفس الكائن في ذاكرة الـ Heap.
٢. الحالة (instance) مقابل Static
متغيرات/دوال الحالة (instance) مرتبطة بكائن محدد. المتغيرات/الدوال الساكنة مرتبطة بالكلاس وتُشارَك بين جميع الحالات.
٣. دوافع Static
استخدم static عندما تحتاج بيانات مشتركة (مثل عدّاد أو مُتتبّع) أو دوال مساعدة عامة لا تحتاج حالة الكائن.
٤. الحزم
الحزم تُنظّم الكلاسات في مجموعات منطقية. المحددات (public/private/default/protected) تتحكم في مدى رؤية كل عضو.
٥. التغليف
اجعل الحقول private. وفّر getters للقراءة وsetters مع التحقق للتعديل. هذا يحمي سلامة الكائن.
٦. مصفوفات الكائنات
المصفوفات تخزّن المراجع لا الكائنات. الكائنات تعيش في الـ Heap. تحقق دائمًا من null قبل الوصول.