بدون فهم بناء جملة Java، من المستحيل أن تصبح مطورًا جادًا، لذلك نواصل اليوم تعلم بناء الجملة. تحدثنا في أحد المقالات السابقة عن المتغيرات البدائية، ولكن بما أن هناك نوعين من المتغيرات، فسنتحدث اليوم عن النوع الثاني - الأنواع المرجعية في Java. إذا ما هو؟ لماذا هناك حاجة إلى أنواع البيانات المرجعية في Java؟ لنتخيل أن لدينا كائنًا تلفزيونيًا يتمتع ببعض الخصائص، مثل رقم القناة ومستوى الصوت والعلم:
public class TV {
int numberOfChannel;
int soundVolume;
boolean isOn;
}
كيف يمكن لنوع بسيط مثل int
تخزين هذه البيانات؟ دعونا نتذكر: متغير واحد int
هو 4 بايت. ولكن يوجد بالداخل متغيران (4 بايت + 4 بايت) من نفس النوع، وأيضًا boolean
(+1 بايت)... الإجمالي - من 4 إلى 9، ولكن كقاعدة عامة، يتم تخزين المزيد من المعلومات في الكائن. ما يجب القيام به؟ لا يمكنك وضع كائن في متغير. في هذه المرحلة من قصتنا، تظهر المتغيرات المرجعية. تقوم المتغيرات المرجعية بتخزين عنوان موقع الذاكرة الذي يوجد به كائن معين. أي أنها "بطاقة عمل" تحتوي على عنوان يمكننا من خلاله العثور على كائننا في الذاكرة المشتركة وإجراء بعض المعالجات به. الإشارة إلى أي كائن في Java هي متغير مرجعي. هذا ما سيبدو عليه كائننا التلفزيوني:
TV telly = new TV();
قمنا بتعيين متغير من النوع TV باسم telly
رابط للكائن الذي تم إنشاؤه من النوع TV. أي أن JVM يخصص الذاكرة على الكومة لكائن التلفزيون، ويقوم بإنشائه والعنوان لموقعه في الذاكرة، ويضعه في المتغير telly
الذي يتم تخزينه على المكدس. يمكنك قراءة المزيد عن الذاكرة، أي المكدس والكثير من المعلومات المفيدة الأخرى، في هذه المحاضرة . متغير من النوع TV وكائن من النوع TV، هل لاحظت؟ وهذا ليس بدون سبب: يجب أن يكون للكائنات من نوع معين متغيرات مقابلة من نفس النوع (لا نحسب تطبيقات الوراثة والواجهة، لكننا الآن لا نأخذ ذلك في الاعتبار). ففي نهاية المطاف، لن نقوم بسكب الحساء في أكواب، أليس كذلك؟ اتضح أن كائننا هو جهاز تلفزيون، والمتغير المرجعي له يشبه لوحة التحكم. باستخدام جهاز التحكم عن بعد هذا يمكننا التفاعل مع كائننا وبياناته. على سبيل المثال، قم بتعيين خصائص جهاز التلفزيون الخاص بنا:
telly.isOn = true;
telly.numberOfChannel = 53;
telly.soundVolume = 20;
استخدمنا هنا العامل النقطي .
للوصول إلى العناصر الداخلية للكائن الذي يشير إليه المتغير والبدء في استخدامها. على سبيل المثال، في السطر الأول قلنا للمتغير telly
: "أعطنا متغيرًا داخليًا isOn
للكائن الذي تشير إليه واضبطه على صحيح" (قم بتشغيل التلفزيون لنا).
إعادة تعريف المتغيرات المرجعية
لنفترض أن لدينا متغيرين من نوع مرجعي والكائنات التي يشيرون إليها:TV firstTV = new TV();
TV secondTV = new TV();
إذا كتبنا:
firstTV = secondTV;
هذا يعني أننا قمنا بتعيين المتغير الأول كقيمة نسخة من العنوان (قيمة بتات العنوان) للكائن الثاني، والآن يشير كلا المتغيرين إلى الكائن الثاني (وبعبارة أخرى، جهازي تحكم عن بعد لنفس الشيء تلفزيون). وفي الوقت نفسه، تم ترك الكائن الأول بدون متغير يشير إليه. ونتيجة لذلك، لدينا كائن لا يمكن الوصول إليه، لأن المتغير كان بمثابة خيط شرطي له، والذي بدونه يتحول إلى قمامة، ويكمن فقط في الذاكرة ويشغل مساحة. سيتم بعد ذلك إتلاف هذا الكائن من الذاكرة بواسطة أداة تجميع مجمعي البيانات المهملة . يمكنك قطع خيط الاتصال بكائن دون رابط آخر:
secondTV = null;
ونتيجة لذلك، سيكون هناك رابط واحد فقط للكائن - firstTV
ولن secondTV
يشير بعد الآن إلى أي شخص (وهذا لا يمنعنا من تعيين رابط لكائن ما مثل التلفزيون في المستقبل).
فئة السلسلة
بشكل منفصل، أود أن أذكر فئة السلسلة . هذه فئة أساسية مصممة لتخزين البيانات المخزنة كسلسلة والعمل معها. مثال:String text = new String("This TV is very loud");
قمنا هنا بتمرير سلسلة ليتم تخزينها في مُنشئ الكائن. لكن لا أحد يفعل ذلك. بعد كل شيء، يمكن إنشاء سلاسل:
String text = "This TV is very loud";
أكثر ملاءمة، أليس كذلك؟ من حيث شعبية الاستخدام، String
فهو ليس أقل شأنا من الأنواع البدائية، لكنه لا يزال فئة، والمتغير الذي يشير إليه ليس نوعا بدائيا، ولكنه نوع مرجعي. لدينا String
هذه القدرة الرائعة على ربط السلاسل:
String text = "This TV" + " is very loud";
ونتيجة لذلك، سوف نحصل على النص مرة أخرى: This TV is very loud
حيث سيتم دمج السطرين في كل واحد، وسوف يشير المتغير إلى هذا النص الكامل. فارق بسيط مهم هو أن String
هذه فئة غير قابلة للتغيير. ماذا يعني ذلك؟ لنأخذ هذا المثال:
String text = "This TV";
text = text + " is very loud";
يبدو أن كل شيء بسيط: نعلن عن متغير ونعطيه قيمة. في السطر التالي نقوم بتغييره. لكننا لا نتغير حقاً. نظرًا لأن هذه فئة غير قابلة للتغيير، فلن يتم تغيير القيمة الأولية في السطر الثاني، ولكن يتم إنشاء قيمة جديدة، والتي تتكون بدورها من القيمة الأولى + " is very loud"
.
الثوابت المرجعية
في المقال عن الأنواع البدائية، تطرقنا إلى موضوع الثوابت. كيف سيتصرف المتغير المرجعي عندما نعلن أنه نهائي ؟final TV telly = new TV();
قد تعتقد أن هذا سيجعل الكائن غير قابل للتغيير. ولكن لا، هذا ليس صحيحا. سيتم ربط المتغير المرجعي مع المُعدِّل final
بكائن معين دون القدرة على إلغاء ربطه بأي شكل من الأشكال (إعادة تعريفه أو مساواته بـ null
). أي أنه بعد تحديد قيمة هذا المتغير، يتم كتابة كود مثل:
telly = new TV();
أو
telly = null;
سوف يسبب خطأ في التجميع. أي final
أنه يعمل فقط على الرابط، وليس له أي تأثير على الكائن نفسه. إذا جعلناه قابلاً للتغيير في البداية، فيمكننا تغيير حالته الداخلية دون أي مشاكل:
telly.soundVolume = 30;
في بعض الأحيان، يتم تعيين المتغيرات على أنها نهائية حتى في وسيطات الطريقة!
public void enableTV (final TV telly){
telly.isOn = true;
}
يتم ذلك بحيث لا يمكن تجاوز هذه الوسائط أثناء عملية كتابة الطريقة، وبالتالي خلق ارتباك أقل. ماذا لو أشرنا إلى final
متغير مرجعي يشير إلى كائن غير قابل للتغيير؟ على سبيل المثال String
:
final String PASSWORD = "password";
ونتيجة لذلك، سنحصل على ثابت، تناظري للثوابت من النوع البدائي، لأنه هنا لا يمكننا إعادة تعريف المرجع ولا تغيير الحالة الداخلية للكائن (البيانات الداخلية).
دعونا نلخص ذلك
- بينما تقوم المتغيرات البسيطة بتخزين بتات القيمة، تقوم المتغيرات المرجعية بتخزين البتات التي تمثل كيفية استرجاع الكائن.
- يتم الإعلان عن مراجع الكائنات لنوع واحد فقط من الكائنات.
- أي فئة في Java هي نوع مرجعي.
- القيمة الافتراضية لأي متغير مرجعي في Java هي
null
. String
هو مثال قياسي لنوع مرجعي. هذه الفئة أيضًا غير قابلة للتغيير.- ترتبط المتغيرات المرجعية مع المُعدِّل
final
بكائن واحد فقط دون إمكانية إعادة التعريف.
GO TO FULL VERSION