Home > جافا Java > Java Always passing by Value

Java Always passing by Value

سبتمبر 1st, 2009 Wajdy Essam

من المفاهيم الأساسية في مفهوم الدوال 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
// 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 .

بالتالي وبسبب هذا النوع من التمرير فإنه:
1) لا يمكن أن تغير الدالة قيمة أي primitive مرسل .
2) تستطيع الدالة تغيير أحد المتغيرات الموجودة في الكائن المرسل (أقصد المؤشر للكائن المرسل) .
3) لا تستطيع الدالة تغيير المؤشر المرسل وجعله يؤشر لكائن أخر .

الأمثله التالية خير برهان للنقاط أعلاه ،، والمثال الأول سوف يثبت صحة النقطة الأولى :
1) لا يمكن أن تغير الدالة قيمة أي primitive مرسل :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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 );
}
}

وكما هو واضح من المخرج التالي :
test1>

فإن قيمة 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
// 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 ;
}

وكما هو واضح من المخرج التالي:
test2

النقطة الثالثة وهي مصدر الخلل :
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
 // 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 ;
}

وكما هو واضح من المخرج التالي :
test3

فأنه لا يمكن عمل swap بين المتغيرات والسبب أننا من الأساس لا يمكن تغيير قيمة أي Primitive type (النقطة الأولى) ، بالنسبة للSwap الثانية بين الكائنات فالذي حصل هو أنه تم تمرير عنواين الكائنات بالقيمة pass by value للدالة swapReference ، وبعدها تم تغيير قيم المعاملات وجعل كل منها يؤشر للأخر ولكن هذا لن يؤثر في المؤشرات في الدالة main والسبب أنه تم ارساله العنواين بالقيمة .

ومن هنا كانت العباره :
Java Always passing by Value

happy java programming :)

Categories: جافا Java Tags:
  1. نوفمبر 20th, 2009 at 09:29 | #1

    مقالة رائعة وجدي
    بأمانة استفدت من النقطة الأخيرة فقط
    طيب ما السبب في تحديد النقطة الأخيرة في جافا
    هل لديك أية أفكار حول الموضوع

    تحياتي

  2. نوفمبر 20th, 2009 at 14:30 | #2

    والله يا علاء لا أدري بالضبط السبب لم قام مصمم اللغه بهذا التحديد في اللغه ،، وجعلها فقط ترسل بالقيمه ،، هكذا عملية swap أمر يجب ان ينساه اي مبرمج جافا وهي عملية بسيطة جدا في بقية اللغات ..

    ربما أراد التبسيط في اللغه ، فمثلا اذا أنشأت كائن داخل دالة وأرسلتة لداله أخرى ، فسوف تتعامل الدالة الأخرى مع هذا الكائن بدون مشاكل وحتى لو انتهي مجال الكائن في الدالة الأولى والسبب أنه تم التمرير بالقيمة ، في لغات اخرى في سي++ فسوف تكون مشكله كبيره وسوف يكون الكائن في الدالة الثانية يؤشر الى كائن تم حذفه (انتهي مجاله ) ..

    هكذا أراد مصمم اللغه البعد عن التعقيد ولكنه بالمقابل أوجد هذا القصور ، ولكنه في الحقيقة ليس بمهم طالما أن هناك حلول أخرى تؤدي نفس النتيجه .

    دمت بخير ،

  3. نوفمبر 25th, 2009 at 16:28 | #3

    فعلاً الموضوع منطقي بعض الشيء
    على العموم هناك الكثير من الأشياء التي لا أعرف لماذا جافا لا تدعمها
    مثل static method لا تستطيع نسخها override

    تحياتي

  4. نوفمبر 25th, 2009 at 17:45 | #4

    مقالة رائعة اخي العزيز شكرا لك

  5. مايو 9th, 2010 at 04:26 | #5

    وجدي انا نجلاء من علوم حاسوب النيلين محتاجه لي سور س java jbuilder للمشروع بتاعي لو عندك رسلو لي

  6. مايو 10th, 2010 at 01:52 | #6

    من الموقع الرئيسي:
    http://www.embarcadero.com/products/jbuilder

    أختر الزر في الأعلى Try Out لتحميل نسخه مجانية لمدة معينة.

    يمكنك استخدام NetBeans او Eclipse مثلا .. عن نفسي أستخدم Netbeans دائما في التطوير،

    بالتوفيق،

Comments are closed.