الوحدة 10: المصفوفات (أحادية البعد وثنائية البعد)

تنظيم البيانات المتشابهة في مجموعات يسهل التعامل معها

المدة: أسبوعان 6 ساعات نظرية 6 ساعات عملية

المقدمة والأهداف التعليمية

بعد إتقانك لمفاهيم التحكم في التدفق (الشروط والحلقات) وكيفية تنظيم الكود باستخدام الدوال، حان الوقت للانتقال إلى موضوع حيوي آخر في البرمجة: **هياكل البيانات (Data Structures)**. وتعتبر **المصفوفات (Arrays)** هي البوابة الأولى والأكثر شيوعًا لدخول هذا العالم.

المصفوفات تمكنك من التعامل مع مجموعات كبيرة من البيانات المتشابهة بكفاءة، مما يفتح آفاقًا جديدة لبناء برامج أكثر قوة وقدرة على معالجة المعلومات المنظمة. ستتعلم في هذه الوحدة كيفية تعريف المصفوفات، التعامل مع عناصرها، واستخدامها في برامجك.

الأهداف:

بنهاية هذه الوحدة، ستكون قادرًا على:

  • فهم مفهوم المصفوفات وأهميتها في تخزين البيانات المتشابهة.
  • التعرف على خصائص المصفوفات (مثل الفهرس الثابت والحجم الثابت).
  • تعريف وإنشاء المصفوفات أحادية البعد في جافا بطرق مختلفة.
  • الوصول إلى عناصر المصفوفة وتعديلها باستخدام الفهارس.
  • استخدام حلقات التكرار (`for` و `foreach`) للمرور على عناصر المصفوفة.
  • فهم كيفية تمرير المصفوفات إلى الدوال وإرجاعها منها.
  • التعامل مع المصفوفات ثنائية البعد (2D Arrays) واستخدامها لتمثيل البيانات الجدولية.
  • استخدام بعض الدوال الجاهزة في جافا لتسهيل التعامل مع المصفوفات (مثل `Arrays.sort`، `Arrays.equals`).
  • حل المشكلات البرمجية التي تتطلب تخزين ومعالجة مجموعات من البيانات.
---

أولاً: تمهيد: لماذا نحتاج المصفوفات؟

عند بناء البرامج، نحتاج في كثير من الأحيان إلى التعامل مع **مجموعة من البيانات المتشابهة**. تخيل أن لديك قائمة بدرجات 10 طلاب، أو أسماء 50 موظفًا، أو بيانات لـ 1000 منتج. بدون مفهوم المصفوفات، كان سيتعين عليك تعريف متغير لكل قيمة على حدة، مما سيجعل الكود طويلاً جداً، صعب القراءة، وأكثر عرضة للأخطاء.

المشكلة بدون مصفوفات:
// تخزين 3 درجات بدون مصفوفات
int grade1 = 85;
int grade2 = 92;
int grade3 = 78;

// ماذا لو كان لدينا 100 درجة؟ سيكون الكود طويلاً جداً!

هنا جاء مفهوم **المصفوفة (Array)** ليحل هذه المشكلة. المصفوفة هي عبارة عن **حاوية** يمكنها الاحتفاظ بمجموعة من القيم **من نفس النوع**، وتكون هذه القيم مخزنة بشكل متجاور في الذاكرة. يمكننا الوصول إلى كل قيمة في المصفوفة بشكل فردي وسريع باستخدام رقم خاص يسمى **الفهرس (Index)**. يبدأ هذا الفهرس دائمًا من 0 ويستمر حتى 'طول المصفوفة ناقص واحد'.

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

تخيل المصفوفة كرف كتب طويل مقسم إلى خانات مرقمة، تبدأ من الخانة رقم 0. كل خانة مخصصة لنوع معين من الكتب (مثلاً، كتب الرياضيات فقط). لو أردت الوصول إلى الكتاب الخامس، ستذهب إلى الخانة رقم 4 (بسبب البدء من الصفر).

---

ثانياً: تعريف المصفوفة وخصائصها

لنغوص أكثر في طبيعة المصفوفات في جافا:

  • **المصفوفة كائن (Object):** في جافا، المصفوفات هي كائنات (Objects)، وهذا يعني أنها تتمتع بخصائص معينة (مثل خاصية `length` التي تخبرنا عن حجمها) وتتصرف ككائنات في الذاكرة.
  • **تخزين قيم من نفس النوع:** هذه نقطة جوهرية. المصفوفة مخصصة لتخزين قيم من نوع بيانات واحد فقط. لا يمكنك وضع أعداد صحيحة ونصوص في نفس المصفوفة. فمثلاً، مصفوفة من نوع `int` ستخزن أعدادًا صحيحة فقط، ومصفوفة من نوع `String` ستخزن نصوصًا فقط.
  • **الوصول بالفهرس (Index):** كل عنصر في المصفوفة يُميز عن الآخر برقم ترتيبي فريد (فهرس). أول عنصر له الفهرس `0`، والثاني `1`، وهكذا. آخر عنصر في المصفوفة سيكون فهرسه `طول_المصفوفة - 1`.
    int[] grades = {90, 85, 95};
    // grades[0] هي 90
    // grades[1] هي 85
    // grades[2] هي 95
    
  • **حجم المصفوفة ثابت:** بمجرد إنشاء المصفوفة وتحديد حجمها (عدد العناصر التي ستحتويها)، لا يمكن تغيير هذا الحجم. إذا أنشأت مصفوفة بحجم 5، فستبقى بحجم 5.
    ملاحظة مهمة:

    هذا لا يعني أن القيم المخزنة داخل المصفوفة لا يمكن تغييرها. يمكنك تغيير قيمة أي عنصر في أي وقت، لكن عدد العناصر (الحجم) يظل ثابتًا.

  • **القيم الابتدائية الافتراضية:** عندما تُنشئ مصفوفة ولا تُعطي قيمًا ابتدائية صريحة لعناصرها، فإن جافا تُعين لها قيمًا افتراضية بناءً على نوع البيانات:
    • لأنواع الأعداد الصحيحة (`int`, `long`, `short`, `byte`): القيمة الافتراضية هي **`0`**.
    • لأنواع الأعداد العشرية (`double`, `float`): القيمة الافتراضية هي **`0.0`**.
    • لنوع القيم المنطقية (`boolean`): القيمة الافتراضية هي **`false`**.
    • للنصوص أو أي كائن آخر (`String` أو أي كلاس): القيمة الافتراضية هي **`null`** (وهي تعني "لا شيء" أو "لا يشير إلى أي كائن").
---

ثالثاً: فوائد استخدام المصفوفات

تقدم المصفوفات العديد من الفوائد التي تجعلها أداة لا غنى عنها في البرمجة:

  • **تقليل عدد المتغيرات المتشابهة:** بدلًا من تعريف متغيرات فردية لكل قطعة بيانات، يمكن جمعها في مصفوفة واحدة مما يجعل الكود أكثر إيجازًا ووضوحًا.
  • **سهولة تعديل القيم ومعالجتها:** باستخدام حلقات التكرار، يمكن بسهولة المرور على جميع عناصر المصفوفة وتعديلها أو معالجتها (مثل حساب مجموعها، إيجاد أكبر قيمة، أو ترتيبها).
  • **إمكانية الوصول السريع لأي عنصر:** بفضل الفهرسة، يمكن الوصول إلى أي عنصر داخل المصفوفة مباشرةً في وقت ثابت، بغض النظر عن حجم المصفوفة.
  • **أساس لهياكل البيانات الأكثر تعقيدًا:** فهم المصفوفات هو خطوة أولى ضرورية لفهم هياكل البيانات الأكثر تقدمًا مثل القوائم المتصلة (Linked Lists) وجداول التجزئة (Hash Tables) وغيرها.
---

رابعاً: مشكلة المصفوفات: الحجم الثابت

على الرغم من مزاياها العديدة، إلا أن المصفوفات تعاني من قيد رئيسي وحيوي يجب الانتباه إليه:

  • **حجمها ثابت ولا يمكن تغييره بعد إنشائها.** هذا يعني أنه إذا أنشأت مصفوفة بحجم معين (مثل 10 عناصر)، فلا يمكنك إضافة عنصر حادي عشر إليها أو إزالة عنصر لتقليص حجمها بعد ذلك.

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

الحل في جافا: مكتبة Collections

لمعالجة هذه المشكلة وتوفير مرونة أكبر في إدارة مجموعات البيانات، وفرت لغة جافا مكتبة قوية تسمى **Collections Framework**. هذه المكتبة تحتوي على هياكل بيانات ديناميكية (مثل `ArrayList` و `LinkedList`) التي تسمح لك بإضافة أو إزالة العناصر بعد الإنشاء، وتعديل حجمها تلقائياً حسب الحاجة. سيتم دراسة هذه المكتبة لاحقاً في مقرر البرمجة المتقدمة.

---

خامساً: تعريف المصفوفات في Java (Declaration)

قبل استخدام المصفوفة، يجب عليك تعريفها (Declaration)، وهو ما يخبر المترجم بنوع البيانات التي ستخزنها هذه المصفوفة. يوجد أكثر من طريقة لتعريف المصفوفة في جافا:

  • **الطريقة المفضلة والأكثر شيوعًا:** ضع الأقواس المربعة `[]` بعد نوع البيانات مباشرة.
    int[] numbers;        // مصفوفة من الأعداد الصحيحة
    String[] names;       // مصفوفة من النصوص
    double[] values;      // مصفوفة من الأعداد العشرية
    
  • **طرق مقبولة أيضًا (لكن الأقل شيوعًا):**
    int [] numbers;       // الأقواس بعد نوع البيانات مع مسافة
    int numbers[];        // الأقواس بعد اسم المتغير (تأتي من لغة C/C++)
    

يمكنك أيضًا تعريف مصفوفات **متعددة الأبعاد**، وأكثرها شيوعًا هي **المصفوفات ثنائية البعد (2D Arrays)**، والتي تتطلب زوجين من الأقواس:

int[][] matrix;       // مصفوفة ثنائية البعد (جدول من الأعداد الصحيحة)
ملاحظة:

عملية التعريف (Declaration) تخبر جافا بأنك ستستخدم مصفوفة، ولكنها **لا تُنشئ المصفوفة فعليًا في الذاكرة** ولا تُخصص لها مساحة بعد. لإنشاء المصفوفة، تحتاج إلى استخدام الكلمة المحجوزة `new`، وهو ما سنتناوله في القسم التالي.

---

سادساً: إنشاء المصفوفة (Create Array)

بعد تعريف المصفوفة، الخطوة التالية هي **إنشاؤها (Initialization)**، أي تخصيص مساحة لها في الذاكرة وإعدادها للاستخدام. يتم ذلك باستخدام الكلمة المحجوزة `new`. يوجد طريقتان رئيسيتان لإنشاء المصفوفة:

1. إنشاء مصفوفة بحجم محدد وتعبئتها لاحقًا (Empty Creation):

في هذه الطريقة، تحدد حجم المصفوفة (عدد العناصر) عند إنشائها. جميع عناصر المصفوفة ستحصل على قيمها الافتراضية بناءً على نوع البيانات.

int[] numbers = new int[5]; 
// تُنشئ مصفوفة اسمها 'numbers' يمكنها تخزين 5 أعداد صحيحة.
// قيمها الابتدائية ستكون {0, 0, 0, 0, 0}
مثال عملي:
public class CreateArrayExample1 {
    public static void main(String[] args) {
        // تعريف وإنشاء مصفوفة من 3 عناصر لتخزين درجات الطلاب
        String[] studentNames = new String[3]; 

        // تعبئة القيم لاحقًا
        studentNames[0] = "علي";
        studentNames[1] = "فاطمة";
        studentNames[2] = "خالد";

        System.out.println("اسم الطالب الأول: " + studentNames[0]);
        System.out.println("اسم الطالب الثاني: " + studentNames[1]);
        System.out.println("اسم الطالب الثالث: " + studentNames[2]);
    }
}

الناتج:

اسم الطالب الأول: علي
اسم الطالب الثاني: فاطمة
اسم الطالب الثالث: خالد

2. إنشاء مصفوفة مع إعطاء قيم مباشرة (Direct Initialization):

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

int[] scores = {10, 20, 30, 40, 50}; 
// تُنشئ مصفوفة اسمها 'scores' بحجم 5 عناصر وتُعين لها القيم المحددة.
مثال عملي:
public class CreateArrayExample2 {
    public static void main(String[] args) {
        // تعريف وإنشاء مصفوفة تحتوي على درجات الطلاب مباشرةً
        int[] studentGrades = {95, 88, 76, 91, 80};

        System.out.println("درجة الطالب الأول: " + studentGrades[0]);
        System.out.println("درجة الطالب الثالث: " + studentGrades[2]);
        System.out.println("درجة الطالب الأخير: " + studentGrades[4]);
    }
}

الناتج:

درجة الطالب الأول: 95
درجة الطالب الثالث: 76
درجة الطالب الأخير: 80
---

سابعاً: شكل المصفوفة في الذاكرة

لفهم كيفية عمل المصفوفات، من المفيد أن نتخيل كيف يتم تخزينها في ذاكرة الحاسوب. عندما تُنشئ مصفوفة، تُخصص لها جافا مساحة **متجاورة** (Contiguous) في الذاكرة. كل "خلية" في هذه المساحة مخصصة لعنصر واحد من عناصر المصفوفة.

الفهرس (Index) هو ما يسمح لجافا بالقفز مباشرة إلى الموقع الصحيح في الذاكرة للوصول إلى العنصر المطلوب.

مثال: مصفوفة `int[] a = {10, 20, 30, 40, 50};`

سيتم تخزين هذه القيم في الذاكرة بهذا الشكل (تخيل أن كل مربع هو "خلية" في الذاكرة):

موقع الذاكرة  | القيمة   | الفهرس
-------------|----------|--------
  (عنوان بدء المصفوفة)
             | 10       | a[0]
             | 20       | a[1]
             | 30       | a[2]
             | 40       | a[3]
             | 50       | a[4]

إذا أردت الوصول إلى `a[2]` (القيمة 30)، فإن جافا تعرف فوراً مكانها في الذاكرة بناءً على موقع بداية المصفوفة ونوع حجم البيانات والفهرس المطلوب.

---

ثامناً: استخدام الحلقات مع المصفوفات

الحلقات (Loops) هي الأداة المثالية للمرور على جميع عناصر المصفوفة أو جزء منها وتنفيذ عملية معينة عليها (مثل طباعة قيمها، جمعها، أو البحث عن قيمة معينة).

1. الحلقة `for` التقليدية:

هذه الطريقة شائعة جدًا وتُستخدم عندما تحتاج إلى الوصول إلى **الفهرس** نفسه لكل عنصر، سواء لطباعته، أو لتعديل قيمة العنصر في ذلك الفهرس. يمكنك استخدام خاصية **`.length`** للمصفوفة للحصول على عدد عناصرها.

مثال: طباعة عناصر مصفوفة وجمعها
public class ForLoopWithArray {
    public static void main(String[] args) {
        int[] numbers = {10, 20, 30, 40, 50};
        int sum = 0;

        System.out.println("طباعة عناصر المصفوفة باستخدام for التقليدية:");
        for(int i = 0; i < numbers.length; i++) { // يبدأ i من 0 ويستمر حتى أقل من numbers.length (أي 4)
            System.out.println("العنصر في الفهرس " + i + " هو: " + numbers[i]);
            sum += numbers[i]; // sum = sum + numbers[i];
        }
        System.out.println("مجموع عناصر المصفوفة هو: " + sum);
    }
}

الناتج المتوقع:

طباعة عناصر المصفوفة باستخدام for التقليدية:
العنصر في الفهرس 0 هو: 10
العنصر في الفهرس 1 هو: 20
العنصر في الفهرس 2 هو: 30
العنصر في الفهرس 3 هو: 40
العنصر في الفهرس 4 هو: 50
مجموع عناصر المصفوفة هو: 150

2. الحلقة `foreach` (Enhanced for loop - منذ Java 1.5):

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

البنية: `for (DataType elementVariable : arrayName) { ... }`

مثال: طباعة عناصر مصفوفة باستخدام foreach
public class ForEachLoopWithArray {
    public static void main(String[] args) {
        String[] names = {"علي", "سارة", "خالد", "ليلى"};

        System.out.println("طباعة عناصر المصفوفة باستخدام foreach:");
        for(String name : names) { // لكل عنصر (name) من نوع String في مصفوفة names
            System.out.println("الاسم: " + name);
        }
    }
}

الناتج المتوقع:

طباعة عناصر المصفوفة باستخدام foreach:
الاسم: علي
الاسم: سارة
الاسم: خالد
الاسم: ليلى
---

تاسعاً: الدوال والمصفوفات (تمرير وإرجاع)

المصفوفات، كونها كائنات في جافا، يمكن تمريرها إلى الدوال كـ **وسائط (Arguments)**، كما يمكن للدوال أن ترجع مصفوفات كـ **قيم مرجعة (Return Values)**. هذا يعزز من تنظيم الكود وقابليته لإعادة الاستخدام.

1. تمرير المصفوفة كوسيط (Passing Array as Argument):

عندما تمرر مصفوفة إلى دالة، فإن ما يتم تمريره هو **مرجع (Reference)** للمصفوفة، وليس نسخة منها. هذا يعني أن أي تغييرات تُجرى على عناصر المصفوفة داخل الدالة ستنعكس على المصفوفة الأصلية في دالة `main` (أو الدالة التي استدعتها).

public class PassArrayToMethod {

    // دالة تستقبل مصفوفة من الأعداد الصحيحة وتطبع عناصرها
    // لاحظ أننا نُمرر 'int[] array' كباراميتر
    public static void printArray(int[] array) {
        System.out.print("عناصر المصفوفة هي: ");
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i] + " ");
        }
        System.out.println(); // لسطر جديد بعد الطباعة
    }

    // دالة لتعديل العنصر الأول في المصفوفة
    public static void modifyFirstElement(int[] arr) {
        System.out.println("تعديل العنصر الأول في المصفوفة داخل الدالة...");
        arr[0] = 99; // تغيير قيمة العنصر الأول
    }

    public static void main(String[] args) {
        int[] myNumbers = {1, 2, 3, 4, 5};
        
        System.out.print("قبل التعديل: ");
        printArray(myNumbers); // طباعة المصفوفة قبل التعديل

        modifyFirstElement(myNumbers); // استدعاء دالة التعديل
        
        System.out.print("بعد التعديل: ");
        printArray(myNumbers); // طباعة المصفوفة بعد التعديل (ستلاحظ أن العنصر الأول تغير)
    }
}

الناتج المتوقع:

قبل التعديل: عناصر المصفوفة هي: 1 2 3 4 5 
تعديل العنصر الأول في المصفوفة داخل الدالة...
بعد التعديل: عناصر المصفوفة هي: 99 2 3 4 5 

2. إرجاع مصفوفة من دالة (Returning Array from Method):

يمكن لدالة أن تُنشئ مصفوفة جديدة داخلها، أو تعدّل على مصفوفة موجودة ثم تُعيدها كقيمة مرجعة. يجب أن يكون نوع الإرجاع للدالة هو نوع المصفوفة (مثال: `int[]` أو `String[]`).

public class ReturnArrayFromMethod {

    // دالة تستقبل مصفوفة وتعيد مصفوفة جديدة معكوسة الترتيب
    public static int[] reverseArray(int[] originalArray) {
        // إنشاء مصفوفة جديدة بنفس حجم المصفوفة الأصلية
        int[] reversedResult = new int[originalArray.length];
        
        // تعبئة المصفوفة الجديدة بالترتيب العكسي
        for (int i = 0; i < originalArray.length; i++) {
            reversedResult[i] = originalArray[originalArray.length - 1 - i];
        }
        return reversedResult; // إرجاع المصفوفة الجديدة
    }

    // دالة مساعدة لطباعة المصفوفة (يمكن استخدامها من المثال السابق)
    public static void printArray(int[] array) {
        System.out.print("عناصر المصفوفة هي: ");
        for (int num : array) { 
            System.out.print(num + " "); 
        }
        System.out.println();
    }

    public static void main(String[] args) {
        int[] original = {10, 20, 30, 40, 50};
        
        System.out.print("المصفوفة الأصلية: ");
        printArray(original);

        int[] reversed = reverseArray(original); // استدعاء الدالة وتخزين المصفوفة المرجعة

        System.out.print("المصفوفة المعكوسة: ");
        printArray(reversed);
    }
}

الناتج المتوقع:

المصفوفة الأصلية: عناصر المصفوفة هي: 10 20 30 40 50 
المصفوفة المعكوسة: عناصر المصفوفة هي: 50 40 30 20 10 
---

عاشراً: المصفوفات ثنائية البعد (2D Arrays)

المصفوفة ثنائية البعد هي في الواقع **مصفوفة من المصفوفات**. يمكن تخيلها كـ **جدول (Table)** أو شبكة تتكون من **صفوف (Rows)** و **أعمدة (Columns)**. تُستخدم لتخزين البيانات التي لها علاقة ثنائية أو تحتاج إلى تمثيل شبكي، مثل مصفوفة لعبة الشطرنج، أو درجات الطلاب في عدة مواد.

لتعريف مصفوفة ثنائية البعد، نستخدم زوجين من الأقواس المربعة `[][]`.

تعريف وإنشاء المصفوفات ثنائية البعد:

يمكن تهيئتها مباشرةً بالشكل التالي:

int[][] matrix = {
    {1, 2, 3},   // الصف 0
    {4, 5, 6},   // الصف 1
    {7, 8, 9}    // الصف 2
};

للوصول إلى عنصر معين، نستخدم فهرس الصف ثم فهرس العمود:
* `matrix[0][0]` → العنصر في الصف الأول، العمود الأول (القيمة 1). * `matrix[1][1]` → العنصر في الصف الثاني، العمود الثاني (القيمة 5). * `matrix[2][0]` → العنصر في الصف الثالث، العمود الأول (القيمة 7).

المرور على عناصر مصفوفة ثنائية البعد باستخدام حلقات متداخلة:

عادةً ما نستخدم حلقتين **`for` متداخلتين (Nested loops)** للمرور على جميع عناصر المصفوفة ثنائية البعد: الحلقة الخارجية للصفوف، والحلقة الداخلية للأعمدة.

مثال: طباعة مصفوفة ثنائية البعد
public class TwoDArrayExample {
    public static void main(String[] args) {
        int[][] gameBoard = {
            {1, 0, 1},
            {0, 1, 0},
            {1, 0, 1}
        };

        System.out.println("طباعة عناصر لوحة اللعب:");
        // الحلقة الخارجية للصفوف
        for (int row = 0; row < gameBoard.length; row++) { 
            // الحلقة الداخلية للأعمدة في الصف الحالي
            for (int col = 0; col < gameBoard[row].length; col++) { 
                System.out.print(gameBoard[row][col] + " ");
            }
            System.out.println(); // بعد الانتهاء من كل صف، ننتقل لسطر جديد
        }
    }
}

الناتج المتوقع:

طباعة عناصر لوحة اللعب:
1 0 1 
0 1 0 
1 0 1 
---

حادي عشر: دوال جاهزة للتعامل مع المصفوفات

توفّر جافا بعض الكلاسات المساعدة التي تحتوي على دوال جاهزة (Built-in Methods) لتسهيل التعامل مع المصفوفات وإجراء عمليات شائعة عليها. أبرز هذه الكلاسات هي **`java.util.Arrays`** و **`java.lang.System`**.

1. الكلاس `java.util.Arrays`

هذا الكلاس يقدم مجموعة واسعة من دوال الخدمة الثابتة (static methods) للعمليات على المصفوفات أحادية البعد. لاستخدامه، تحتاج عادةً إلى استيراده في بداية ملف جافا الخاص بك: `import java.util.Arrays;`

  • **`Arrays.sort(array)`:** تقوم بترتيب عناصر المصفوفة تصاعديًا.
    import java.util.Arrays;
    
    public class ArraySortExample {
        public static void main(String[] args) {
            int[] nums = {5, 2, 8, 1, 9};
            Arrays.sort(nums); // ترتب المصفوفة: الآن nums ستكون {1, 2, 5, 8, 9}
            System.out.println("المصفوفة بعد الترتيب: " + Arrays.toString(nums));
        }
    }
    

    الناتج: `المصفوفة بعد الترتيب: [1, 2, 5, 8, 9]`

  • **`Arrays.equals(a1, a2)`:** تقارن بين مصفوفتين وتُرجع `true` إذا كانتا متطابقتين تمامًا في المحتوى والترتيب، و `false` بخلاف ذلك.
    import java.util.Arrays;
    
    public class ArrayEqualsExample {
        public static void main(String[] args) {
            int[] arr1 = {1, 2, 3};
            int[] arr2 = {1, 2, 3};
            int[] arr3 = {3, 2, 1};
            System.out.println("هل arr1 تساوي arr2؟ " + Arrays.equals(arr1, arr2)); // الناتج: true
            System.out.println("هل arr1 تساوي arr3؟ " + Arrays.equals(arr1, arr3)); // الناتج: false
        }
    }
    
  • **`Arrays.fill(array, value)`:** تقوم بتعبئة جميع عناصر المصفوفة بقيمة معينة.
    import java.util.Arrays;
    
    public class ArrayFillExample {
        public static void main(String[] args) {
            int[] data = new int[5]; // مصفوفة بحجم 5 عناصر
            Arrays.fill(data, 100); // تعبئة جميع العناصر بالقيمة 100
            System.out.println("المصفوفة بعد التعبئة: " + Arrays.toString(data)); // الناتج: [100, 100, 100, 100, 100]
        }
    }
    
  • **`Arrays.toString(array)`:** تُعد هذه الدالة مفيدة جدًا لطباعة محتوى المصفوفة بشكل سهل القراءة. تُرجع تمثيلاً نصيًا للمصفوفة (مثل `[1, 2, 3]`).
    import java.util.Arrays;
    
    public class ArrayToStringExample {
        public static void main(String[] args) {
            int[] numbers = {10, 20, 30};
            System.out.println("المصفوفة كنص: " + Arrays.toString(numbers)); // الناتج: المصفوفة كنص: [10, 20, 30]
        }
    }
    

2. الكلاس `java.lang.System`

يحتوي هذا الكلاس على دالة واحدة مهمة لنسخ المصفوفات، وهي دالة ثابتة (static) لا تحتاج لاستيرادها صراحةً لأنها جزء من حزمة `java.lang` التي تُستورد تلقائيًا.

  • **`System.arraycopy(src, srcPos, dest, destPos, length)`:** تقوم بنسخ جزء من مصفوفة المصدر (`src`) إلى مصفوفة الوجهة (`dest`).
    • `src`: المصفوفة المصدر التي ستُنسخ منها العناصر.
    • `srcPos`: الفهرس البدء للنسخ من المصفوفة المصدر.
    • `dest`: المصفوفة الوجهة التي ستُلصق فيها العناصر.
    • `destPos`: الفهرس البدء للنسخ إلى المصفوفة الوجهة.
    • `length`: عدد العناصر المراد نسخها.
    import java.util.Arrays; // نحتاجها لطباعة المصفوفة بشكل سهل
    
    public class ArrayCopyExample {
        public static void main(String[] args) {
            int[] source = {1, 2, 3, 4, 5};
            int[] destination = new int[5]; // يجب أن تكون مصفوفة الوجهة موجودة مسبقًا
    
            // نسخ جميع العناصر من source (بدءًا من الفهرس 0) إلى destination (بدءًا من الفهرس 0)، بطول source.length
            System.arraycopy(source, 0, destination, 0, source.length);
            
            System.out.println("المصفوفة المصدر: " + Arrays.toString(source));
            System.out.println("المصفوفة الوجهة بعد النسخ: " + Arrays.toString(destination));
        }
    }
    

    الناتج:

    المصفوفة المصدر: [1, 2, 3, 4, 5]
    المصفوفة الوجهة بعد النسخ: [1, 2, 3, 4, 5]
    
---

ثاني عشر: تمارين مقترحة 💡

اختبر فهمك لمفهوم المصفوفات من خلال حل التمارين التالية. حاول كتابة الكود بنفسك قبل البحث عن الحلول!

1. تمرين: حساب مجموع عناصر مصفوفة

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

public class ArraySum {
    public static void main(String[] args) {
        // إنشاء وتعبئة المصفوفة
        int[] numbers = {10, 20, 30, 40, 50}; // مثال
        
        int sum = 0;
        // اكتب الكود هنا لحساب المجموع باستخدام حلقة for أو foreach
        
        System.out.println("مجموع عناصر المصفوفة هو: " + sum);
    }
}

2. تمرين: البحث عن أكبر عنصر في مصفوفة

اكتب دالة اسمها `findMax` تستقبل مصفوفة من الأعداد الصحيحة كباراميتر، وترجع أكبر قيمة موجودة في هذه المصفوفة. ثم استدعِ هذه الدالة من `main` واطبع القيمة الكبرى.

public class FindMaxInArray {

    // اكتب الدالة findMax هنا
    // مثال: public static int findMax(int[] arr) { ... }

    public static void main(String[] args) {
        int[] data = {15, 7, 22, 9, 30, 1};
        // استدعِ الدالة findMax واطبع النتيجة
        // مثال: System.out.println("أكبر قيمة في المصفوفة هي: " + findMax(data));
    }
}

3. تمرين: طباعة مصفوفة ثنائية البعد بشكل منظم

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

public class PrintTwoDArray {
    public static void main(String[] args) {
        int[][] myMatrix = {
            {10, 11, 12},
            {20, 21, 22},
            {30, 31, 32}
        };

        System.out.println("طباعة المصفوفة ثنائية البعد:");
        // اكتب الكود هنا لطباعة المصفوفة باستخدام حلقات متداخلة
    }
}

4. تمرين تحدي: عكس ترتيب مصفوفة (دون إنشاء مصفوفة جديدة)

اكتب دالة اسمها `reverseInPlace` تستقبل مصفوفة من الأعداد الصحيحة كباراميتر، وتقوم بعكس ترتيب عناصر هذه المصفوفة **دون إنشاء مصفوفة جديدة**. أي يجب أن يتم التعديل على المصفوفة الأصلية مباشرةً. (تلميح: استخدم متغيرًا مؤقتًا لتبديل القيم).

import java.util.Arrays;

public class ReverseArrayInPlace {

    // اكتب الدالة reverseInPlace هنا
    // مثال: public static void reverseInPlace(int[] arr) { ... }

    public static void main(String[] args) {
        int[] originalArray = {1, 2, 3, 4, 5, 6};
        System.out.println("المصفوفة الأصلية: " + Arrays.toString(originalArray));
        
        // استدعِ الدالة reverseInPlace
        // مثال: reverseInPlace(originalArray);
        
        System.out.println("المصفوفة بعد العكس: " + Arrays.toString(originalArray));
    }
}
```
---

ثالث عشر: ملخص الوحدة

لقد اكتسبتَ في هذه الوحدة الحاسمة فهمًا قوياً لمفهوم **المصفوفات (Arrays)** في جافا، وهي حجر الزاوية في التعامل مع البيانات المجمعة.

  • تعلمنا أن المصفوفات هي كائنات تخزن مجموعة من القيم من **نفس النوع** في مواقع ذاكرة متجاورة، ويتم الوصول إليها عبر **الفهارس** التي تبدأ من الصفر.
  • فهمنا أن **حجم المصفوفة ثابت** بعد إنشائها، ولكن يمكن التغلب على هذا القيد باستخدام هياكل بيانات ديناميكية (مثل `ArrayList`) في مكتبة Collections Framework التي ستدرسها لاحقًا.
  • استكشفنا طرق **تعريف وإنشاء المصفوفات** أحادية البعد (سواء بتحديد الحجم أو بالتهيئة المباشرة للقيم).
  • أتقنا استخدام **حلقات `for` التقليدية و `foreach`** للمرور على عناصر المصفوفة بكفاءة.
  • عرفنا كيف يمكن **تمرير المصفوفات كـوسائط للدوال** وكيف يمكن **للدوال أن ترجع مصفوفات** كقيم.
  • تعرفنا على **المصفوفات ثنائية البعد (2D Arrays)** وكيفية التعامل معها باستخدام الحلقات المتداخلة لتمثيل البيانات الجدولية.
  • استعرضنا بعض **الدوال الجاهزة والمفيدة** في الكلاسات `java.util.Arrays` و `java.lang.System` لتسهيل العمليات على المصفوفات (مثل الترتيب والمقارنة والنسخ).

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

في الوحدة القادمة، سننتقل إلى أحد أهم وأقوى مفاهيم البرمجة الكائنية (OOP) في جافا: **الكائنات والكلاسات (Objects and Classes)**، وهو ما سيأخذ قدراتك البرمجية إلى مستوى جديد تماماً.

---

المراجع

  • موقع Oracle Java Documentation.
  • "Java: A Beginner's Guide" – Herbert Schildt.
  • "Head First Java" – Kathy Sierra, Bert Bates.