قبل قراءة هذه المقالة، بناءً على تجربتك وفهمك، يمكنك أولاً التفكير في طريقة تمرير المعلمات لوظائف Java واختيارها:
أ. هل يتم تمريرها بالقيمة؟
ب. مرت بالإشارة؟
ج. جزئيا من حيث القيمة وجزئيا من خلال المرجع؟
لن يتم الإعلان عن الإجابة الصحيحة هنا بعد، وسندعك تجد الإجابة بنفسك من خلال مثال بسيط:
1. حدد أولاً قيمة النوع
قيمة فئة ثابتة عامة {قيمة سلسلة خاصة = "قيمة"؛ public String getValue() { قيمة الإرجاع } public void setValue(String value) { this.value = value } }
2. اكتب وظيفتين newValue و AdjustValue: ستوجه newValue معلمة الإدخال إلى كائن جديد، وستستدعي AdjustValue طريقة setValue لمعلمة الإدخال لتعديل قيمة الكائن.
public static void newValue(Value value) { value = new Value(); value.setValue("قيمة جديدة"); " + value.getValue()); } public static void AdjustValue(Value value) { value.setValue("قيمة جديدة"); System.out.println("في تعديل القيمة, HashCode = " + value.hashCode() + "، value = " + value.getValue()); }
3. رمز اختبار بسيط
public static void main(String[] args) { Value value1 = new Value(); System.out.println("قبل التعديل، HashCode = " + value1.hashCode() + "، value = " + value1.getValue() ); // أشر القيمة 1 إلى كائن القيمة الجديد newValue(value1 System.out.println("بعد التعديل، HashCode = " + value1.hashCode() +" "، value = " + value1.getValue() + "/n"); Value value2 = new Value(); " + value2.getValue()); // استخدم طريقة تعيين الكائن لتعديل القيمة الداخلية للكائن modValue(value2); System.out.println("بعد التعديل، HashCode = " + value2.hashCode() + ", value = " + value2.getValue());
4. سجل نتائج التنفيذ:
قبل التعديل، HashCode = 12677476، القيمة = القيمة في newValue، HashCode = 33263331، القيمة = قيمة جديدة بعد التعديل، HashCode = 12677476، القيمة = القيمة قبل التعديل، HashCode = 6413875، القيمة = القيمة في AdjustValue، HashCode = 6413875، القيمة = القيمة الجديدة بعد التعديل، HashCode = 6413875، القيمة = القيمة الجديدة
5. تحليل النتائج:
الكود أعلاه هو نمط برمجة شائع جدًا: حدد |. احفظ |. احصل على قيمة أو كائن في المحيط، وقم بتمرير الكائن كمعلمة إلى الطريقة، وقم بتعديل خصائص الكائن وسلوكه في الطريقة. ومع ذلك، فإن طريقتي التعديل newValue وmodeValue مختلفتان بعد استدعاء الطريقة، يبدو الكائن مختلفًا تمامًا من الخارج! كيف نفهم هذا الاختلاف؟ دعونا أولاً نراجع مفهومي التمرير بالقيمة والتمرير بالرجوع:
* المرور بالقيمة يعني أنه عند تمرير وسيطة إلى دالة، تتلقى الدالة نسخة من القيمة الأصلية. ولذلك، إذا قامت الدالة بتعديل المعلمة، فسيتم تغيير النسخة فقط، بينما تظل القيمة الأصلية دون تغيير.
* المرور بالمرجع يعني أنه عند تمرير وسيطة إلى دالة، تتلقى الدالة عنوان الذاكرة الخاص بالقيمة الأصلية بدلاً من نسخة من القيمة. لذلك، إذا قامت الدالة بتعديل المعلمة، فإن القيمة الأصلية للمعلمة (في رمز الاتصال خارج كتلة الوظيفة) تتغير أيضًا.
الإجابة الصحيحة: أ - تقوم وظائف Java بتمرير المعلمات حسب القيمة!
تحليل السجل:
* في القسم الأول من إخراج السجل، يتم تغيير معلمة القيمة 1 للإشارة إلى كائن جديد داخل طريقة newValue، ويتم إخراج رمز التجزئة وقيمة الكائن الجديد، ومع ذلك، بعد القفز من مجال طريقة newValue، لا يوجد تغيير يحدث للقيمة 1 في الطريقة الرئيسية، وهذا يتوافق مع تعريف وخصائص التمرير حسب القيمة؛
* يوضح إخراج السجل الثاني أن القيمة 2 تنفذ عملية setValue داخل طريقة تعديل القيمة. يظل رمز التجزئة بدون تغيير ولكن يتم تعديل القيمة بعد مغادرة مجال طريقة تعديل القيمة، تتغير القيمة 2 في الطريقة الرئيسية. يمكن للأشخاص الذين استخدموا C++ فهم هذه الظاهرة بسهولة على أنها: تمرير معلمات الوظيفة حسب المرجع! لأن هذا مشابه جدًا للتمرير حسب المرجع في C++! ولكن هذا هو بالضبط المكان الذي من المرجح أن يقع فيه سوء الفهم!
المبدأ الخفي وراء الظواهر المختلفة للسجلين هو أن لغة Java تمرر المعلمات حسب القيمة والكائنات التي يتم تشغيلها في Java هي في الواقع إشارات إلى كائنات التشغيل، ويتم تخزين الكائنات نفسها في "الكومة". ويتم تخزين "مرجع" الكائن في سجل أو "مكدس".
يصف الكود الكاذب الفرق بين طريقة newValue وطريقة AdjustValue:
newValue{ Value_ref2 = value_ref1; // قم بإدخال القيمة المرجعية value_ref1 واحصل على نسخة من value_ref1 value_obj2 = new Value(); // يتم إنشاء value_obj2 وتهيئته في "الكومة" value_ref2 -> value_obj2; value_obj2 value_ref2 ->value_obj2.setValue("xxx"); // value_obj2 تم تعديل القيمة printValueObj2(); // ما يتم طباعته هنا هو قيمة obj2} AdjustValue{ Value_ref2 = value_ref1 // قم بتمرير القيمة المرجعية value_ref1 واحصل على نسخة من value_ref1 value_ref2 ->value_obj1.setValue("xxx" "); // تم تعديل قيمة value_obj1 printValueObj1(); // ما تتم طباعته هنا هو قيمة obj1}
هذا واضح بما فيه الكفاية! عندما يتم تمرير value1_ref1 إلى الوظيفة كمعلمة، يتم أولاً نسخ نسخة من value1_ref2 لاستخدامها في مجال الوظيفة. في هذا الوقت، يشير كلا المرجعين إلى نفس كود value_obj في دالة newObject [value = new Value(); ] في الواقع، تشير value1_ref1 إلى كائن جديد value_obj2؛ والعمليات المحددة بعد ذلك هي جميع العمليات على الكائن الجديد؛ تعمل وظيفة AdjustValue مباشرة على value_obj1 من خلال طريقة التعيين، والتي تختلف عن وظيفة newValue.
تمرير المعلمات حسب القيمة
عند استدعاء أسلوب ما، يجب عليك توفير المعلمات، ويجب عليك توفيرها بالترتيب المحدد في قائمة المعلمات.
على سبيل المثال، تقوم الطريقة التالية بطباعة رسالة n مرات متتالية:
public static void nPrintln(String message, int n) { for (int i = 0; i < n; i++) System.out.println(message);}
مثال يوضح المثال التالي تأثير التمرير حسب القيمة.
يقوم هذا البرنامج بإنشاء طريقة لتبادل متغيرين.
public class TestPassByValue { public static void main(String[] args) { int num1 = 1; int num2 = 2; // استدعاء طريقة المبادلة Swap(num1, num2); System.out.println("بعد طريقة المبادلة، يكون الرقم 1" + num1 + " and num2 is " + num2); } /** طريقة تبديل متغيرين*/ public static void Swap(int n1, int n2) { System.out.println("/tداخل طريقة المبادلة"); .out.println("/t/tقبل تبديل n1 هو " + n1 + " n2 هو " + n2); // مبادلة قيم n1 و n2 int temp = n1 = n2 = temp; System.out.println("/t/t بعد تبديل n1 هو " + n1 + " n2 هو " + n2 });
نتائج التجميع والتشغيل للمثال أعلاه هي كما يلي:
قبل طريقة المبادلة، num1 هو 1 و num2 هو 2 داخل طريقة المبادلة قبل المبادلة n1 هو 1 n2 هو 2 بعد المبادلة n1 هو 2 n2 هو 1 بعد طريقة المبادلة، num1 هو 1 و num2 هو 2
استدعاء طريقة المبادلة لتمرير معلمتين. ومن المثير للاهتمام أن قيم المعلمات الفعلية لا تتغير بعد استدعاء الطريقة.