يقولون "من الأمور المميزة في جافا أنها تلغي المؤشرات" ولكن هذا بصراحة كلام بعيد كل البعد عن الدقة !
فكل الكائنات في جافا مؤشرات !!
بل إن الكلام الصحيح ... جافا لا تسمح للمستخدم بــ"عدم" استعمال المؤشرات ..
حتى أن كلمة كائن غير دقيقة ! إن الكائن موجود في مكان ما من الذاكرة أما ما نستخدمه فهو مؤشر إلى الكائن !
في نهاية هذه المشاركة .. أرجو أن أقنعك بأن تقول .. مؤشر للكائن بدل العبارة غير الدقيقة : كائن .
كيف نشرح عملية الإسناد بالشكل الــ"غير دقيق" :
عندما نقوم بإسناد كائن إلى كائن آخر باستخدام عملية الإسناد = .. فإن الكائنين يصبحان كائنا واحدا والتغييرات في أحدهما ستكون هي نفس التغييرات في الآخر لأن لهما نفس المكان في الذاكرة
لماذا كل هذا اللف والدوران ؟ .. الكلام السليم هو ما يلي :
الكائن هو نفسه ولكن المؤشرين مختلفين إلى نفس الكائن ..
بعبارة أخرى ... حتى تقوم بأخذ قيمة (أو قيم متغيرات ) الكائن .. فعليك إنشاء كائن جديد ثم نسخ القيم واحدا واحداً من الكائن القديم بنفس الطريقة (كل ما صادفت كائن داخل الكائن المطلوب نسخه تعيد نفس العلمية )
ببساطة هذه نفس الطريقة التي نسند بها المؤشرات للكائنات إلى بعضها البعض .. ونفس الطريقة التي نمرر فيها المؤشرات إلى التوابع ! (في اللغات التي تعترف باستخدام المؤشرات بشكل صريح )
فيما يلي مثال بسيط يبين تمرير الوسطاء والإسناد .. كيف أنه يتم باستخدام المرجعية (العنوان ) (المؤشرات :) )
طبعا قبل أن ندخل الى المثال يجب أن نعلم أن انواع البيانات الاساسية ليست كائنات وبالتالي ليست مؤشرات بل نتعامل معها مباشرة .. أي ان المتغير int x يدل على نفسه وليس على مؤشر يشير لعنوان آخر في الذاكرة يحوي عددا صحيحا
المثال هو كما يلي .. خطوة خطوة ..
ليكن لدينا الصف التالي :
class Ref
{
public int x=0;
public JButton B=new JButton("Hello");
public int passX()
{
return x;
}
public JButton passB()
{
return B;
}
public void changeInt(int w)
{
w=5;
}
public void changeButton(JButton w)
{
w.setText("What is Your Name?");
}
}التابعان التاليان يعيد أحدهما قيمة x والآخر يعيد الكائن B ( والهدف من هذه المشاركة أن نعرف ما الذي يعيده بالضبط ؟ ) public int passX()
{
return x;
}
public JButton passB()
{
return B;
}والتابعان الآخران يقومان بتغيير قيمة الوسيط الممرر .. public void changeInt(int w)
{
w=5;
}
public void changeButton(JButton w)
{
w.setText("What is Your Name?");
} ملاحظة : استعملت كائن الزر JButton لأنه الأكثرشهرة في الكتب .. كلما أراد أحد التحدث عن كائن .. ذكر الأزرار كأوضح مثال .. ولكننا سنستخدم نص الزر فقط في المثال .والآن لنختبر الصف كما يلي :
بداية لا ننسى كتابة :
import javax.swing.JButton;
public class TestReference {
public static void main(String[] args) { والآن لنبدأنقوم ببناء كائن جديد .. والتأكد من القيم الأصلية للكائنات
Ref R=new Ref();
int outX=R.passX(); System.out.println(outX);
JButton outB=R.passB(); System.out.println(outB.getText()); لاحظ أن المتحول outX (يفترض) أن يحتفظ بقيمة المتحول x داخل الكائن R.. ولكن في مكان مختلف من الذاكرة (أي سيكون لدينا نسختان مختلفتا العنوان )كما أن المتحول outB (يفترض) أن يكون هو نفسه الكائن B داخل الكائن R .. طبعا كما ذكرت في البداية هذا الكلام غير دقيق لأن outB الآن هو مؤشر يحتفظ بعنوان الكائن ...( أبق هذا في ذهنك دائما )
وسيكون الخرج كما هو متوقع :
0 Helloوالآن لنحاول أن نعدل على القيمة الصحيحة (وهي نوع أساسي وليست كائناً ) ثم نرى هل تغيرت القيمة داخل الكائن أو لا :
outX=1;
System.out.println(outX+ " " + R.passX()); والخرج :1 0لا تغيير داخل الكائن .. التغيير فقط في المتحول الذي غيرناه ( كما هو متوقع )
ماذا لو غيرنا الكائن outB :
outB.setText("Shut Up !");
System.out.println(outB.getText()+ " " + R.passB().getText()); الخرج المفاجئ :Shut Up ! Shut Up !تغير الكائن B بداخل الكائن R رغم أننا غيرنا الكائن outB فقط !
وهذا مصداق قولنا أننا نتعامل مع مؤشرين يؤشران لنفس الكائن وأن العملية = التي قمنا بها في البداية قامت بإسناد عناوين الذاكرة بين المؤشرين فقط لا غير ! لا يوجد نسخ للقيم ... وفي الذاكرة هناك مكان واحد يحتفظ بالكائن وليس مكانان مختلفان
لنجرب تمرير الوسطاء :
R.changeInt(outX);System.out.println(outX+ " " + R.passX());
R.changeButton(outB);System.out.println(outB.getText()+ " " + R.passB().getText()); ربما لن يفاجئك الآن الخرج التالي :1 0 What is Your Name? What is Your Name?القيمة الصحية تم تمريرها بـ(القيمة ) أي أن التابع changeInt تعامل مع نسخة لها مكان آخر في الذاكرة ..
بينما تم تمرير الكائن بالمرجع (أي تم تمرير عنوان الكائن الذي سنتعامل معه ) بالتالي لم ننسخ نسخة أخرى ونعدل عليه بل عدلنا النسخة الوحيدة !
أخيرا هذا مثال بسيط لتأكيد أن الإسناد هو إسناد لعنوان الكائن وليس لمحتوياته .. وبمجرد إسناد مؤشر لآخر .. ستفقد بيانات الكائن الأول من يؤشر إليها .. وسنترك لململم النفايات garbage collector مهمة تحرير مكانها من الذاكرة ..
Ref q=new Ref();q.x=100;
System.out.println(q);
Ref w=new Ref();w.x=684;
System.out.println(w);
w=q;
System.out.println(w.x);
System.out.println(w);
System.out.println(q); اقرأ الخرج بعناية وانتبه كيف أن طباعة كائن هي طباعة عنوانه .. تماماً لو كنا نطبع مؤشراً .. سنطبع قيمة العنوان الذي يؤشر إليه! Ref@a59698
Ref@141d683
100
Ref@a59698
Ref@a59698 لاحظ كيف صار للمؤشرين القيمة ذاتها ... ولا أريد أن يقول أحد صار للكائنين العنوان ذاته ..لنتوقف عن قول كائنات .. ولنقل مؤشرات للكائنات .. فليس من المنطق نسف الدقة من أجل اختصار الكلام ..
والحمد لله رب العالمين
