English 🇬🇧

المُنشئات وكلمة this

إعطاء الحياة للكائنات من لحظة إنشائها
CPCS203 - Module 04: Objects and Classes

1 لماذا نحتاج المُنشئات؟

في الدرس السابق، رأينا كيفية إنشاء صنف Circle بسيط. لكن لإنشاء دائرة قابلة للاستخدام، كان علينا دائمًا القيام بـ خطوتين:

الطريقة القديمة - خطوتان
Circle c1 = new Circle(); // Step 1: Create object c1.radius = 5.0; // Step 2: Set the radius

هذا يعمل، ولكن يبدو محرجًا بعض الشيء. ماذا لو نسينا الخطوة الثانية؟ عندها سنحصل على دائرة بنصف قطر صفر، وهذا ليس منطقيًا في الغالب.

ألن يكون من الأفضل إنشاء الكائن وتعيين نصف القطر في خطوة واحدة؟

الطريقة الجديدة - خطوة واحدة
Circle c1 = new Circle(5.0); // Both create AND initialize!

هذا أفضل بكثير! وهذا بالضبط ما تفعله المُنشئات (constructors). المُنشئ هو طريقة خاصة تُستدعى تلقائيًا عند إنشاء كائن جديد باستخدام الكلمة المفتاحية new.

الدافع وراء المُنشئات

المُنشئات تسمح لنا بـ:

  • التهيئة عند الإنشاء: تعيين القيم الأولية للكائن مباشرة عند إنشائه
  • ضمان حالة صالحة: التأكد من أن جميع الكائنات تُنشأ في حالة صحيحة وقابلة للاستخدام
  • صفات إلزامية: إجبار المستخدمين على تقديم قيم ضرورية معينة
  • كائنات غير قابلة للتغيير: إنشاء كائنات لا يمكن تغييرها بعد الإنشاء

2 ما المميز في المُنشئ؟

المُنشئ يبدو مثل طريقة عادية، ولكن مع بعض القواعد الخاصة:

🎯 قواعد المُنشئ
  • الاسم: يجب أن يكون نفس اسم الصنف تمامًا (بما في ذلك الحروف الكبيرة/الصغيرة)
  • لا نوع إرجاع: المُنشئ لا يحتوي على نوع إرجاع، حتى ولا void
  • يُستدعى بـ new: يُستدعى تلقائيًا عند استخدام new
  • الغرض: تهيئة حقول البيانات للكائن الجديد

لنكتب صنف Circle مع مُنشئ:

Circle.java
public class Circle { // Data field double radius; // Constructor - same name as class, no return type public Circle(double r) { radius = r; // Initialize radius with the parameter } // Method to compute area public double getArea() { return 3.14159 * radius * radius; } }

الآن يمكننا إنشاء دائرة في خطوة واحدة:

TestCircle.java
public class TestCircle { public static void main(String[] args) { Circle c1 = new Circle(5.0); // Constructor is called! System.out.println("Area: " + c1.getArea()); } }
ناتج التنفيذ
Area: 78.53975
ملاحظة

عند كتابة new Circle(5.0)، يحدث شيئان:

  • ١. تُنشئ Java كائنًا جديدًا من نوع Circle في الذاكرة
  • ٢. تستدعي Java المُنشئ Circle(double r) وتمرر 5.0 كمعامل

3 هل يمكن أن يحتوي الصنف على أكثر من مُنشئ؟

نعم! يمكن للصنف أن يحتوي على مُنشئات متعددة. هذا يسمى التحميل الزائد للمُنشئ (constructor overloading). لنفترض أننا نريد إضافة لون إلى دائرتنا:

Circle.java - مع مُنشئات متعددة
public class Circle { double radius; String color; // Constructor 1: Only radius (default color to "red") public Circle(double r) { radius = r; color = "red"; } // Constructor 2: Both radius and color public Circle(double r, String c) { radius = r; color = c; } // Constructor 3: Only color (default radius to 1.0) public Circle(String c) { radius = 1.0; color = c; } // Constructor 4: No parameters (default both) public Circle() { radius = 1.0; color = "red"; } }

الآن يمكننا إنشاء دوائر بطرق مختلفة:

استخدام مُنشئات مختلفة
Circle c1 = new Circle(5.0); // radius=5.0, color="red" Circle c2 = new Circle(3.0, "blue"); // radius=3.0, color="blue" Circle c3 = new Circle("green"); // radius=1.0, color="green" Circle c4 = new Circle(); // radius=1.0, color="red"
التحميل الزائد للمُنشئ

المُنشئات المتعددة تعطي مرونة للمستخدمين. يمكنهم اختيار المُنشئ الأنسب لاحتياجاتهم:

  • إذا كانوا يهتمون فقط بنصف القطر → استخدم Circle(double r)
  • إذا كانوا يريدون تحديد كليهما → استخدم Circle(double r, String c)
  • إذا كانوا يريدون القيم الافتراضية → استخدم Circle()

4 المُنشئ الافتراضي — مفهوم مهم جدًّا

لنعد إلى المثال الأول من الدرس السابق، عندما كتبنا صنف Circle بدون أي مُنشئ على الإطلاق:

Circle.java - بدون مُنشئ صريح
public class Circle { double radius; public double getArea() { return 3.14159 * radius * radius; } }

ومع ذلك، تمكنا من كتابة:

Circle c1 = new Circle(); // This worked! But how?

كيف نجح هذا إذا لم نكتب مُنشئًا؟ الإجابة: إذا لم تكتب أي مُنشئ، فإن Java توفر تلقائيًا مُنشئًا افتراضيًا (default constructor) لك.

قاعدة مهمة جدًّا

المُنشئ الافتراضي يختفي عند تعريف أي مُنشئ!

إذا كتبت أي مُنشئ (حتى مُنشئ واحد فقط)، فإن Java لن توفر لك المُنشئ الافتراضي تلقائيًا.

لنرى مثالًا:

Circle.java - مع مُنشئ واحد فقط
public class Circle { double radius; // We provide ONE constructor public Circle(double r) { radius = r; } }

الآن، إذا حاولنا كتابة:

Circle c1 = new Circle(); // ❌ ERROR! No such constructor exists! Circle c2 = new Circle(5.0); // This works

إذا كنت تريد كلا المُنشئين، يجب عليك كتابتهما بشكل صريح:

Circle.java - كلا المُنشئين صريحين
public class Circle { double radius; // Explicit no-argument constructor public Circle() { radius = 1.0; // default radius } // Constructor with parameter public Circle(double r) { radius = r; } }
🔸 المُنشئ الافتراضي الضمني
  • توفره Java تلقائيًا
  • لا معاملات
  • لا يفعل شيئًا (جسم فارغ)
  • يختفي إذا عرّفت أي مُنشئ
  • غير مرئي في الكود
🔹 المُنشئ الافتراضي الصريح
  • تكتبه أنت بنفسك
  • لا معاملات
  • يمكن أن يحتوي على كود تهيئة
  • موجود حتى مع مُنشئات أخرى
  • مرئي في الكود

5 كلمة this — "أنا أتحدث عن نفسي"

هناك مشكلة شائعة عند كتابة المُنشئات. لنلقِ نظرة على هذا الكود:

Circle.java - مشكلة في الأسماء
public class Circle { double radius; // Data field public Circle(double r) { radius = r; // OK, but 'r' is a weird name } }

الاسم r يعمل، لكنه ليس وصفيًا. من الأفضل استخدام radius كاسم المعامل أيضًا:

محاولة - استخدام نفس الاسم
public class Circle { double radius; // Data field public Circle(double radius) { // Parameter named 'radius' radius = radius; // ❌ Problem! Which radius is which? } }

هذا غامض! الآن لدينا اسمان radius:

  • حقل البيانات radius (متغير الكائن)
  • المعامل radius (متغير المُنشئ)

عندما تكتب radius = radius، فإن Java تعتبر كلاهما يشير إلى المعامل! حقل البيانات يكون "مخفيًا" (shadowed).

🎯 الحل: كلمة this

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

Circle.java - الحل الصحيح
public class Circle { double radius; // Data field public Circle(double radius) { this.radius = radius; // Clear: object field = parameter // 'this.radius' = data field of current object // 'radius' = parameter } }

الآن الكود واضح تمامًا:

  • this.radius يشير إلى حقل البيانات للكائن
  • radius (بدون this) يشير إلى المعامل
فهم this

فكّر في this كأنه كلمة "أنا" أو "نفسي". عندما يقول الكائن this.radius، فهو يقول: "نصف قطري الخاص".

في الواقع، this هو مرجع (reference) يحمل عنوان الكائن الحالي في الذاكرة. على سبيل المثال، إذا كان الكائن موجودًا في العنوان 0x3A7F، فإن this يحتوي على 0x3A7F.

مثال شامل مع عدة حقول:

Circle.java - مع عدة حقول
public class Circle { double radius; String color; int id; public Circle(double radius, String color, int id) { this.radius = radius; // Object's radius = parameter radius this.color = color; // Object's color = parameter color this.id = id; // Object's id = parameter id } }

6 استخدام this() لاستدعاء مُنشئ آخر

هناك استخدام آخر لـ this: يمكنك استخدام this() لاستدعاء مُنشئ آخر من نفس الصنف. هذا مفيد لتجنب تكرار الكود.

تذكر المثال من القسم 3 حيث كان لدينا 4 مُنشئات؟ كان هناك الكثير من التكرار. لنعيد كتابته باستخدام this():

قبل - تكرار الكود
public class Circle { double radius; String color; public Circle(double r) { radius = r; color = "red"; // Repeated } public Circle(double r, String c) { radius = r; // Repeated color = c; // Repeated } public Circle(String c) { radius = 1.0; // Repeated color = c; } public Circle() { radius = 1.0; // Repeated color = "red"; // Repeated } }
بعد - استخدام this() لتجنب التكرار
public class Circle { double radius; String color; // Main constructor - does all the work public Circle(double radius, String color) { this.radius = radius; this.color = color; } // Other constructors call the main one public Circle(double radius) { this(radius, "red"); // Call Circle(double, String) } public Circle(String color) { this(1.0, color); // Call Circle(double, String) } public Circle() { this(1.0, "red"); // Call Circle(double, String) } }
مبدأ DRY: Don't Repeat Yourself

استخدام this() يتبع مبدأ "لا تكرر نفسك" (DRY). الآن، منطق التهيئة موجود في مكان واحد فقط (المُنشئ الرئيسي). إذا احتجنا لتغيير كيفية التهيئة، نغيره في مكان واحد فقط.

قواعد استخدام this()
  • يجب أن يكون أول تعليمة: استدعاء this() يجب أن يكون السطر الأول في المُنشئ
  • فقط في المُنشئات: لا يمكن استخدام this() إلا داخل مُنشئ
  • مرة واحدة فقط: يمكنك استدعاء مُنشئ واحد فقط (لا يمكنك كتابة this() مرتين)
❌ خطأ - this() ليس أول تعليمة
public Circle(double radius) { System.out.println("Creating circle"); // ❌ Code before this() this(radius, "red"); // ❌ ERROR! this() must be first }
صحيح - this() هو أول تعليمة
public Circle(double radius) { this(radius, "red"); // Correct: first statement System.out.println("Circle created"); // Code after is OK }
🔸 this.field
  • يشير إلى حقل الكائن
  • يُستخدم لحل تعارض الأسماء
  • يمكن استخدامه في أي مكان
  • مثال: this.radius = radius;
🔹 this(...)
  • يستدعي مُنشئًا آخر
  • يُستخدم لتجنب تكرار الكود
  • فقط في المُنشئات
  • يجب أن يكون أول تعليمة
  • مثال: this(1.0, "red");

7 متغيرات المرجع للكائنات

الآن بعد أن فهمنا المُنشئات وكلمة this، لنلقِ نظرة فاحصة على ما يحدث فعلًا عندما نكتب سطرًا مثل هذا:

Circle c1 = new Circle(5.0, "blue");

هذا السطر الواحد يفعل ثلاثة أشياء:

  • Circle c1 — يُعلن عن متغير اسمه c1 من النوع Circle. هذا المتغير يُسمى متغير مرجع. لا يحمل الكائن نفسه — بل يحمل مرجعًا (مؤشرًا) إلى مكان وجود الكائن في الذاكرة.
  • new Circle(5.0, "blue") — ينشئ كائنًا جديدًا من نوع Circle في الذاكرة، يستدعي المُنشئ لتهيئته بـ radius = 5.0 و color = "blue"، ويُرجع مرجعًا للكائن المُنشأ حديثًا.
  • = — يُسند ذلك المرجع إلى المتغير c1. الآن c1 يشير إلى (يؤشر إلى) الكائن.

لنصور هذا في الذاكرة:

Variable
c1
0x3A7F
متغير مرجع
(يحمل مرجعًا للكائن)
Circle Object (0x3A7F)
radius: 5.0
color: "blue"
computeArea() method
الكائن الفعلي في الذاكرة
(يحتوي على حقول البيانات والطرق)
فهم أساسي

المتغير c1 ليس هو الكائن. إنه جهاز تحكم عن بُعد يشير إلى الكائن. الكائن نفسه يعيش في مكان ما في الذاكرة، وc1 يحمل مرجعًا إليه. لهذا يُسمى متغير مرجع — لأنه يشير إلى (يؤشر إلى) الكائن.

لهذا السبب أيضًا تعمل كلمة this بهذه الطريقة: داخل الكائن، this يحمل نفس المرجع (0x3A7F)، مما يسمح للكائن بالإشارة إلى نفسه.

8 الوصول إلى أعضاء الكائن

الآن بعد أن أصبح لدينا متغير مرجع يشير إلى كائن، كيف نتفاعل معه؟ كيف نقرأ بياناته أو نستدعي طرقه؟

الإجابة هي عامل النقطة (.) — المسمى أيضًا عامل الوصول للأعضاء.

الصيغة بسيطة: referenceVariable.member

الوصول إلى الأعضاء عبر عامل النقطة
Circle c1 = new Circle(5.0, "blue"); // Calling a method (behavior) double area = c1.computeArea(); // c1.computeArea() → calls the method // Calling the getter (read the data) double r = c1.getRadius(); // c1.getRadius() → returns 5.0 // Calling the setter (modify the data) c1.setRadius(7.5); // c1.setRadius(7.5) → changes the radius System.out.println("Radius: " + c1.getRadius()); // 7.5 System.out.println("Area: " + c1.computeArea()); // 176.71
ماذا تعني النقطة؟

فكّر في النقطة (.) على أنها تقول "اذهب إلى الكائن والوصول إلى...". عندما تكتب c1.getRadius()، فأنت تخبر Java: "اذهب إلى الكائن الذي يشير إليه c1، واستدعِ طريقته getRadius()."

متغير المرجع هو البوابة. النقطة هي الباب. والعضو (حقل أو طريقة) هو ما تصل إليه على الجانب الآخر.

9 القيم الافتراضية لحقول البيانات

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

القيم الافتراضية لحقول البيانات
نوع البيانات القيمة الافتراضية
int, long, short, byte 0
double, float 0.0
boolean false
char '\u0000'
Reference types (String, Object, etc.) null

لنرَ هذا عمليًا. لنأخذ صنفًا يحتوي على عدة حقول لا يُهيئها المُنشئ:

Student.java - بدون تهيئة في المُنشئ
public class Student { private String name; private int age; private double gpa; private boolean graduated; private char grade; // No constructor defined — Java provides the implicit default constructor public void printInfo() { System.out.println("Name: " + name); System.out.println("Age: " + age); System.out.println("GPA: " + gpa); System.out.println("Graduated: " + graduated); System.out.println("Grade: ['" + grade + "']"); } }
Main.java - الطباعة بدون تهيئة
Student s = new Student(); s.printInfo();
الناتج
Name: null Age: 0 GPA: 0.0 Graduated: false Grade: ['']

كل حقل له قيمته الافتراضية — حتى مع أننا لم نحدد أيًا منها. Java تُهيئ جميع حقول البيانات تلقائيًا.

حقول البيانات مقابل المتغيرات المحلية: فرق حاسم

الآن نصل لنقطة يحدث فيها الالتباس كثيرًا للطلاب. القاعدة أعلاه تنطبق فقط على حقول البيانات (الحقول المُعلنة داخل الصنف). لا تنطبق على المتغيرات المحلية (المتغيرات المُعلنة داخل طريقة).

Main.java - متغير محلي بدون تهيئة
public class Main { public static void main(String[] args) { int x; // local variable — NOT initialized System.out.println(x); // ERROR! "variable x might not have been initialized" } }
هذا لن يُترجَم!

Java ترفض ترجمة هذا الكود. المتغير المحلي x تم الإعلان عنه ولكن لم يُسند له قيمة أبدًا. بخلاف حقول البيانات، المتغيرات المحلية لا تحصل على قيم افتراضية. يجب عليك تهيئتها بنفسك قبل استخدامها.

🔹 حقول البيانات (مستوى الصنف)
  • تُعلن داخل الصنف، خارج أي طريقة
  • تُهيأ تلقائيًا بـ قيم افتراضية
  • يمكن استخدامها مباشرة دون تهيئة يدوية
  • مثال: private int age; ← افتراضيًا 0
🔸 المتغيرات المحلية (مستوى الطريقة)
  • تُعلن داخل طريقة
  • لا قيمة افتراضية — غير مُهيأة
  • يجب تهيئتها قبل الاستخدام، وإلا لن يُترجَم الكود
  • مثال: int x; ← خطأ في الترجمة إذا استُخدم قبل الإسناد

10 الأنواع البدائية مقابل الأنواع المرجعية

الآن لنفهم فرقًا مهمًا جدًّا في كيفية تعامل Java مع المتغيرات البدائية (مثل int، double) مقابل متغيرات المرجع (مثل Circle، String). هذا الفرق يؤثر على ما يحدث عند إسناد متغير إلى آخر.

المتغيرات البدائية: القيم يتم نسخها

مع الأنواع البدائية، كل متغير يحمل قيمته الخاصة المستقلة. عندما تُسند أحدهما للآخر، القيمة يتم نسخها:

إسناد بدائي
int i = 5; int j = i; // j gets a COPY of i's value // Now: i = 5, j = 5 (two independent copies) i = 10; // changing i does NOT affect j System.out.println("i = " + i); // i = 10 System.out.println("j = " + j); // j = 5 — unchanged!
بعد int i = 5; int j = i;
i
5
نُسخت إلى
j
5
بعد i = 10;
i
10
مستقلة
j
5

كل متغير له صندوقه الخاص بقيمته الخاصة. تغيير أحدهما لا يؤثر على الآخر.

متغيرات المرجع: المراجع يتم نسخها

مع الأنواع المرجعية، السلوك مختلف جذريًا. المتغير لا يحمل الكائن — بل يحمل مرجعًا للكائن. عندما تُسند متغير مرجع إلى آخر، فأنت تنسخ المرجع، وليس الكائن نفسه.

إسناد مرجعي
Circle c1 = new Circle(5.0, "blue"); Circle c2 = new Circle(10.0, "red");
بعد إنشاء c1 و c2:
c1
0x1A2B
Circle Object (0x1A2B)
radius: 5.0
color: "blue"
c2
0x7F3E
Circle Object (0x7F3E)
radius: 10.0
color: "red"

الآن راقب ما يحدث عندما نُسند c1 إلى c2:

c2 = c1; // c2 now holds the SAME reference as c1
بعد c2 = c1;
c1
0x1A2B
Circle Object (0x1A2B)
radius: 5.0
color: "blue"
c2
0x1A2B
Circle Object (0x7F3E)
radius: 10.0
color: "red"
لا يوجد متغير يشير هنا بعد الآن!
هذا الكائن لا يزال في الذاكرة، يشغل مساحة، لكنه غير قابل للوصول — لا يوجد متغير مرجع يشير إليه.
ماذا حدث؟

بعد c2 = c1، المتغير c2 لم يعد يشير إلى الدائرة ذات radius = 10.0. بدلاً من ذلك، كلا c1 و c2 يشيران الآن إلى نفس الكائن (الدائرة عند 0x1A2B مع radius = 5.0).

هذا يعني إذا عدّلنا الكائن عبر c2، فإن التغيير سيكون مرئيًا عبر c1 أيضًا — لأنهما يشيران إلى نفس الكائن تمامًا:

c2.setRadius(99.0); System.out.println(c1.getRadius()); // 99.0 — c1 sees the change! System.out.println(c2.getRadius()); // 99.0 — same object

جامع النفايات: ماذا يحدث للكائن المهجور؟

لكن ماذا عن الدائرة الأصلية التي كان c2 يشير إليها — تلك التي مع radius = 10.0 و color = "red"؟ لا تزال موجودة في الذاكرة، تشغل مساحة، لكن لا يوجد متغير يشير إليها. ليس لدينا طريقة للوصول إليها بعد الآن.

جمع النفايات (Garbage Collection)

Java لديها نظام مدمج يُسمى جامع النفايات (Garbage Collector) يفحص الذاكرة بشكل دوري بحثًا عن كائنات ليس لها متغير مرجع يشير إليها. عندما يجد مثل هذا الكائن اليتيم، يقوم تلقائيًا بتحرير الذاكرة التي يشغلها حتى يمكن إعادة استخدام الذاكرة.

هذا يحدث تلقائيًا في الخلفية — المبرمج لا يحتاج للقلق بشأنه، ولا يحتاج لحذف الكائنات يدويًا، ولا يحتاج لكتابة أي كود تنظيف. جامع النفايات يعتني بكل شيء. هذه واحدة من المزايا العظيمة لـ Java مقارنة بلغات مثل C و C++، حيث المبرمج مسؤول عن تحرير الذاكرة يدويًا.

Circle Object (0x7F3E)
radius: 10.0
color: "red"
جامع النفايات
يكتشف الكائن غير القابل للوصول، يستعيد الذاكرة، ويحرر المساحة تلقائيًا.
ملخص: البدائية مقابل المرجعية

المتغيرات البدائية تحمل قيمًا فعلية. إسناد إحداها للأخرى ينسخ القيمة. كل متغير مستقل.

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

11 أصناف مدمجة مفيدة: Date و Random

Java تأتي مع مئات الأصناف الجاهزة في مكتبتها القياسية. لا أحد يُتوقع منه حفظها جميعًا — لكن كمبرمج Java، من مسؤوليتك استكشاف وتعلم تلك التي تحتاجها. توثيق Java الرسمي (Javadoc) هو دليلك.

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

صنف Date

صنف java.util.Date يمثل نقطة زمنية محددة. إنشاء كائن Date بدون معاملات يعطيك التاريخ والوقت الحاليين:

import java.util.Date; Date now = new Date(); System.out.println(now); // Output: Wed Feb 11 14:30:25 AST 2026 (current date and time)
ملاحظة

صنف Date هو واحد من أقدم الأصناف في Java. للعمل الأكثر تقدمًا مع التاريخ/الوقت، توفر Java أصنافًا أحدث مثل LocalDate، LocalTime، و LocalDateTime في حزمة java.time. لكن Date نقطة انطلاق جيدة لفهم كيفية عمل الكائنات.

صنف Random

صنف java.util.Random يُولّد أرقامًا عشوائية زائفة. لنبدأ بمثال بسيط:

import java.util.Random; Random rand = new Random(); System.out.println(rand.nextInt(100)); // random int from 0 to 99 System.out.println(rand.nextInt(100)); // another random int from 0 to 99 System.out.println(rand.nextDouble()); // random double from 0.0 to 1.0

في كل مرة تشغل فيها هذا البرنامج، تحصل على أرقام مختلفة. لكن الآن، لنجرب شيئًا مثيرًا...

البذرة: لماذا يمكن لبرنامجين إنتاج نفس الأرقام "العشوائية"

إليك سؤال يفاجئ العديد من المبتدئين: هل هذه الأرقام عشوائية حقًا؟ الإجابة هي لا. تُسمى أرقامًا عشوائية زائفة — يتم توليدها بواسطة معادلة رياضية، وليس بواسطة عشوائية حقيقية.

المعادلة تعمل هكذا: تبدأ برقم أولي يُسمى بذرة (seed). من تلك البذرة، تطبق حسابًا رياضيًا لإنتاج الرقم "العشوائي" الأول. ثم تغذي تلك النتيجة مرة أخرى في المعادلة لإنتاج الرقم الثاني، وهكذا. كل رقم يُحدد بواسطة الرقم السابق، مكونًا سلسلة.

كيف تعمل الأرقام العشوائية الزائفة

فكر فيها هكذا:

seed → formula → number₁ → formula → number₂ → formula → number₃ → ...

الفكرة الأساسية هي: إذا بدأ كائنا Random بـ نفس البذرة، فسينتجان نفس التسلسل تمامًا من الأرقام. كل مرة. لأن المعادلة نفسها، ونقطة البداية نفسها.

لنثبت ذلك:

نفس البذرة = نفس الأرقام
Random rand1 = new Random(42); // seed = 42 Random rand2 = new Random(42); // seed = 42 (same!) System.out.println("rand1: " + rand1.nextInt(100)); // 0 System.out.println("rand2: " + rand2.nextInt(100)); // 0 — same! System.out.println("rand1: " + rand1.nextInt(100)); // 68 System.out.println("rand2: " + rand2.nextInt(100)); // 68 — same! System.out.println("rand1: " + rand1.nextInt(100)); // 54 System.out.println("rand2: " + rand2.nextInt(100)); // 54 — same again!
لماذا هما متطابقان؟

كلا rand1 و rand2 تم إنشاؤهما بـ نفس البذرة (42). بما أن المعادلة الرياضية نفسها ونقطة البداية نفسها، فإنهما ينتجان نفس التسلسل تمامًا من الأرقام: 0، 68، 54، ... كل مرة، على كل جهاز، إلى الأبد.

شغّل هذا البرنامج 100 مرة — ستحصل دائمًا على نفس الناتج. الأرقام حتمية، وليست عشوائية.

الآن قارن هذا مع استخدام بدون بذرة:

بدون بذرة = أرقام مختلفة في كل تشغيل
Random rand = new Random(); // no seed — Java uses the current time as seed System.out.println(rand.nextInt(100)); // different every time you run! System.out.println(rand.nextInt(100)); // different every time you run!
متى تستخدم بذرة؟

إذا أردت نتائج مختلفة في كل مرة (ألعاب، محاكاة)، استخدم new Random() بدون بذرة — Java ستستخدم تلقائيًا الوقت الحالي كبذرة، والذي يختلف في كل تشغيل.

إذا أردت نتائج قابلة لإعادة الإنتاج (اختبار، تصحيح أخطاء، تجارب علمية)، استخدم new Random(seed) مع بذرة محددة — هذا يضمن نفس التسلسل في كل مرة، مما يجعل نتائجك متوقعة وقابلة للتحقق.

12 الملخص

ما تعلمناه
  • المُنشئات تُهيئ الكائنات في لحظة إنشائها — تعطي الحياة للكائنات.
  • المُنشئ يجب أن يكون له نفس اسم الصنف وليس له نوع إرجاع.
  • الصنف يمكن أن يحتوي على مُنشئات متعددة (التحميل الزائد للمُنشئ).
  • كل صنف في Java له مُنشئ واحد على الأقل — المُنشئ الافتراضي الضمني يختفي إذا عُرّف أي مُنشئ.
  • this.field يشير إلى حقل البيانات للكائن الحالي، يحل تعارض الأسماء مع المتغيرات المحلية.
  • this(args) يستدعي مُنشئًا آخر في نفس الصنف (سلسلة المُنشئات).
  • متغير المرجع يحمل مرجعًا لكائن، وليس الكائن نفسه.
  • عامل النقطة (.) يُستخدم للوصول إلى حقول وطرق الكائن.
  • حقول البيانات تحصل على قيم افتراضية تلقائيًا؛ المتغيرات المحلية لا.
  • إسناد مرجع لآخر ينسخ المرجع — كلاهما يشير إلى نفس الكائن. الكائن المهجور يتم تنظيفه بواسطة جامع النفايات.
  • صنف Random يولد أرقامًا عشوائية زائفة باستخدام معادلة رياضية. نفس البذرة = نفس التسلسل.
ماذا بعد؟

في الدرس القادم، سنستكشف الأعضاء النسخية مقابل الأعضاء الساكنة (instance vs. static)، وكيفية العمل مع مصفوفات من الكائنات. سنستكشف أيضًا تصاميم أصناف أكثر تعقيدًا مع كائنات متعددة متفاعلة.

جامعة الملك عبدالعزيز
كلية الحاسبات وتقنية المعلومات (FCIT)
إعداد د. عبدالمحسن المعلوي
آخر تحديث: ١١ فبراير ٢٠٢٦