Java: الإرسال بالمرجع أم بالقيمة؟ تحليل كامل

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: المعركة الأبدية "بالمرجع" مقابل "بالقيمة"



إذا كنت قد كتبت بلغة Java ولو مرة واحدة، وقمت بإرسال مصفوفة أو كائن إلى دالة، ثم تفاجأت بتغير البيانات الأصلية — فأنت لست وحدك. هذا السؤال يؤرق المبتدئين (وليس فقط) منذ ظهور اللغة. على Stack Overflow، حصد ملايين المشاهدات، والنقاشات في التعليقات لا تهدأ منذ سنوات.



اليوم سنحسم الأمر مرة واحدة وإلى الأبد: هل Java هي "pass-by-reference" أم "pass-by-value"؟ تحذير: الإجابة واضحة، لكن الشيطان، كالعادة، في التفاصيل. لن نقدم مجرد تعريف جاف، بل سنعرض بأمثلة حقيقية كيفية عمل إرسال الوسائط في Java، ولماذا يختلط الأمر على الكثيرين.



ماذا يقول مبتكرو Java؟



الوثائق الرسمية وجيمس غوسلينغ نفسه (مبتكر Java) لا يتركون مجالًا للنقاش: Java ترسل دائمًا الوسائط بالقيمة (pass-by-value). لا استثناءات. لا للأنواع البدائية ولا للكائنات.



ولكن لماذا يغير الكود الكائنات؟ لماذا يتغير List أو المصفوفة المرسلة إلى دالة؟ دعنا نفهم.



Pass-by-value: ماذا يعني هذا بالضبط؟



بدايةً — تعريف واضح. Pass-by-value يعني أنه عند استدعاء دالة، يتم إنشاء نسخة من قيمة المتغير، وهذه النسخة هي التي تُرسل إلى الدالة. المتغير الأصلي (في الكود المستدعي) يبقى دون تغيير.



Pass-by-reference — هو عندما تُرسل إلى الدالة مرجع إلى المتغير الأصلي، ويمكن للدالة تغيير قيمته مباشرة (على سبيل المثال، استبدال الكائن). هذا غير موجود في Java.



الآن — إلى الأمثلة.



مثال 1: الأنواع البدائية (int, double, boolean)



مع الأنواع البدائية، الأمر واضح. لنكتب كودًا بسيطًا:



public class Main {    public static void main(String[] args) {        int x = 10;        changePrimitive(x);        System.out.println("بعد الدالة x = " + x); // 10    }

public static void changePrimitive(int value) { value = 20; // نغير النسخة }}


النتيجة:

بعد الدالة x = 10


كل شيء منطقي: أرسلنا نسخة من الرقم 10، داخل الدالة غيرنا النسخة إلى 20، لكن الأصل بقي كما هو. هذا pass-by-value خالص.



مثال 2: الكائنات (سلاسل نصية، قوائم، كلاسات خاصة)



والآن الجزء المثير للاهتمام. انظر:



public class Main {    public static void main(String[] args) {        StringBuilder sb = new StringBuilder("Hello");        changeStringBuilder(sb);        System.out.println("بعد الدالة: " + sb); // Hello World!    }

public static void changeStringBuilder(StringBuilder builder) { builder.append(" World!"); // نغير محتوى الكائن }}


النتيجة:

بعد الدالة: Hello World!


«آه! — ستقول. — الكائن تغير! إذن هذا هو pass-by-reference!»



لا. هذا هو الفخ الذي يقع فيه 90% من المبتدئين. دعنا نفهم ما حدث بالفعل.



ماذا يُخزَّن في المتغير sb؟



في Java، المتغير من نوع كائن (مثل StringBuilder، List، String) لا يخزن الكائن نفسه. إنه يخزن مرجعًا (عنوانًا) للكائن في الذاكرة. الكائن نفسه موجود في مكان ما في الكومة (heap).



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



لكن! إذا حاولت تعيين كائن جديد للمعامل داخل الدالة، فلن يتغير المرجع الأصلي. لنثبت ذلك:



public class Main {    public static void main(String[] args) {        StringBuilder sb = new StringBuilder("Hello");        changeReference(sb);        S                

المدونات

توصيات الكتب