من المفاهيم الأساسية في مفهوم الدوال Functions/Methods في لغات البرمجة هو تمرير الوسائط Parameter Passing ، وبشكل عام هناك طريقتين للتمرير المعاملات الأولى هي التمرير بالقيمة Pass by Value والثانية هي Pass by Reference .
عند استخدام النوع الأول Pass by Value سوف يتم نسخ القيمة من المتغير أو الكائن المرسل الى المعامل الموجود في الدالة التي تم استدعائها ، وأي تغيير يتم اجرائه على هذا المعامل فلن يتأثر المتغير الأول والسبب أنه تم تمرير القيمة فقط .
أما عند استخدام النوع الثاني Pass by Reference فسوف يتم ارسال عنوان Pointer/Reference المتغير أو الكائن الى المعامل الموجود في الدالة ، وسوف يكون هذا المعامل يؤشر للمتغير أو الكائن وبالتالي أي تغيير يتم اجرائه على المعامل سوف يتغير المتغير أو الكائن تبعا لذلك ، لأنهم الإثنين يؤشران لنفس المنطقة في الذاكرة .
تقريبا أي مبرمج ولو حتى مبتدئ يعرف هذا الكلام بشكل جيد ، ولكن هناك فهم غير دقيق misconception خاصه عند خلط الحديث بين التمرير في لغه مثل سي/سي++ مع الجافا ، ومصدر هذه المشكلة هو أنه في لغه سي++ يمكنك تنشيء كائن object يوجد في المكدس Stack أو يمكنك أنشاء الكائن في الHeap وتتعامل معه من خلال Pointer أو Reference . بينما في الجافا دائما الكائنات تتواجد في ال Heap وتتعامل معها من خلال الReference.
مثال :
1 2 3 4 5 6 7 8 |
// in C++ Student st("Ahmed",15,80); // this object created in stack Student* st2 = new Student("Ahmed",15,80); // this object created in heap // in Java Student st3 = new Student("ahmed",15,80); // this object created in heap |
بالنظر الى المثال السابق سنجد في مثال سي++ أن st يسمى كائن Object وst2 يسمى مؤشر لكائن Pointer to Object ، بينما في مثال الجافا فإن الst3 يسمى Reference to Object حيث أن الكائن موجود في الذاكرة Heap و st3 هو مجرد مؤشر لتلك المنطقة .
نأتي الأن لموضوع التمرير Passing ، ففي لغات سي\سي++ فهي تسمح بأن يتم ارسال المتغير أو الكائن من خلال طريقتين Value or Reference ، بينما في الجافا يتم دائما الإرسال بالقيمة سواء لمتغير أو لReference to Object .
بالتالي وبسبب هذا النوع من التمرير فإنه:
- لا يمكن أن تغير الدالة قيمة أي primitive مرسل .
- تستطيع الدالة تغيير أحد المتغيرات الموجودة في الكائن المرسل (أقصد المؤشر للكائن المرسل) .
- لا تستطيع الدالة تغيير المؤشر المرسل وجعله يؤشر لكائن أخر .
الأمثله التالية خير برهان للنقاط أعلاه ،، والمثال الأول سوف يثبت صحة النقطة الأولى
1) لا يمكن أن تغير الدالة قيمة أي primitive مرسل :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// Test Passing primitive varaible in Java public class TestPassing { public static void main (String[] args) { int x = 4 ; System.out.println("Before Calling ChangeX x = " + x ); ChangeX(x); System.out.println("After Calling ChangeX x = " + x ); } public static void ChangeX (int x ) { x = x * 2 ; System.out.println("in ChangeX x = " + x ); } } |
وكما هو واضح من المخرج التالي :
فإن قيمة x لم تتغير بعد استدعاء دالة التغيير ، وهكذا تم اثبات النقطة الأولى .
2) تستطيع الدالة تغيير أحد المتغيرات الموجودة في الكائن المرسل (أقصد المؤشر للكائن المرسل)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// Test Change Object state through object reference public class TestPassing2 { public static void main (String[] args) { Student st = new Student("Ahmed",20); System.out.println("Before Call ChangeStudent st : " + st); ChangeStudent(st); System.out.println("Before Call ChangeStudent st : " + st); } private static void ChangeStudent (Student st ) { st.setAge(100); System.out.println("in ChangeStudent st : " + st); } } class Student { public Student (String name , int age) { this.name = name ; this.age = age; } public void setAge (int age) { this.age = age ; } public String toString () { return String.format(name + " , " + age) ; } private String name ; private int age ; } |
وكما هو واضح من المخرج التالي:
3) لا تستطيع الدالة تغيير المؤشر المرسل وجعله يؤشر لكائن أخر (مصدر الخلل) .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
// Test Swap reference and varaible public class TestPassing3 { public static void main (String[] args ) { int x = 5 , y = 10 ; System.out.println("Before Calling SwapVar : x = " + x + " y = " + y ); SwapVar(x,y); System.out.println("After Calling SwapVar : x = " + x + " y = " + y ); System.out.println("\n\n"); Student s1 = new Student("Ahmed",10); Student s2 = new Student("Ali",20); System.out.println("Befor Calling SwapReference : " + s1 + " " + s2); SwapReference(s1,s2); System.out.println("After Calling SwapReference : " + s1 + " " + s2); } public static void SwapVar (int x ,int y) { int tmp = x ; x = y; y = tmp; System.out.println("in SwapVar : x = " + x + " y = " + y ); } public static void SwapReference (Student st1 , Student st2 ) { Student tmp = st1 ; st1 = st2 ; st2 = tmp ; System.out.println("in SwapReference : " + st1 + " " + st2); } } class Student { public Student (String name , int age) { this.name = name ; this.age = age; } public void setAge (int age) { this.age = age ; } public String toString () { return String.format(name + " , " + age) ; } private String name ; private int age ; } |
وكما هو واضح من المخرج التالي :
فأنه لا يمكن عمل swap بين المتغيرات والسبب أننا من الأساس لا يمكن تغيير قيمة أي Primitive type (النقطة الأولى) ، بالنسبة للSwap الثانية بين الكائنات فالذي حصل هو أنه تم تمرير عنواين الكائنات بالقيمة pass by value للدالة swapReference ، وبعدها تم تغيير قيم المعاملات وجعل كل منها يؤشر للأخر ولكن هذا لن يؤثر في المؤشرات في الدالة main والسبب أنه تم ارساله العنواين بالقيمة .
ومن هنا كانت العباره :
Java Always passing by Value
(1595)
أشكرك أستاذ عصام، حقيقة لم أكن أعلم ذلك قبل قراءة الدرس، أشكرك على المقال الواضح والمعلومة الجديدة
معلومات رائعه ..