المقدمة
هذا المقال يقدم نظرة عامة على كيفية إنشاء صنف لا يمكن تغييره في برمجة جافا.
الكائن غير قابل للتغيير عندما لا يتغير حالته بعد تهيئته. على سبيل المثال، String
هو صنف لا يمكن تغييره و، بمجرد تحقيقه، لا يتغير قيمة كائن String
أبدًا. تعرف على المزيد حول لماذا يكون صنف String
لا يمكن تغييره في جافا.
نظرًا لعدم قدرة الكائن غير القابل للتغيير على التحديث، تحتاج البرامج إلى إنشاء كائن جديد لكل تغيير في الحالة. ومع ذلك، يحمل الكائنات غير القابلة للتغيير أيضًا الفوائد التالية:
- الصنف اللا يمكن تغييره جيد لأغراض التخزين المؤقت لأنك لا تحتاج إلى القلق بشأن تغيير القيمة.
- الصنف اللا يمكن تغييره آمن تلقائيًا مع الخيوط، لذا لا داعي للقلق بشأن سلامة الخيوط في بيئات متعددة الخيوط.
تعرف على المزيد حول التعددية في جافا وتصفح أسئلة مقابلات التعددية في جافا.
إنشاء صنف لا يمكن تغييره في لغة البرمجة جافا
لإنشاء صنف لا يمكن تغييره في لغة البرمجة جافا، يجب عليك اتباع هذه المبادئ العامة:
- أعلن الصنف كـ
final
لكي لا يمكن تمديده. - اجعل جميع الحقول
private
حتى لا يُسمح بالوصول المباشر إليها. - لا توفر أساليب تعيين (setter methods) للمتغيرات.
- اجعل جميع الحقول القابلة للتغيير (mutable fields)
final
حتى يمكن تعيين قيمة الحقل مرة واحدة فقط. - قم بتهيئة جميع الحقول باستخدام أسلوب البناء (constructor) الذي يقوم بعملية النسخ العميق (deep copy).
- قم بعملية النسخ (cloning) للكائنات في أساليب الحصول (getter methods) لتعيين نسخة بدلاً من إرجاع مرجع الكائن الفعلي.
الصنف التالي هو مثال يوضح أساسيات عدم التغيير. يعرف صنف FinalClassExample
الحقول ويوفر أسلوب البناء الذي يستخدم النسخ العميق لتهيئة الكائن. يقوم الكود في أسلوب main
في ملف FinalClassExample.java
باختبار عدم التغيير في الكائن.
أنشئ ملفًا جديدًا بالاسم FinalClassExample.java
وانسخ فيه الكود التالي:
import java.util.HashMap;
import java.util.Iterator;
public final class FinalClassExample {
// حقول فئة FinalClassExample
private final int id;
private final String name;
private final HashMap<String,String> testMap;
public int getId() {
return id;
}
public String getName() {
return name;
}
// دالة الحصول على للكائنات القابلة للتغيير
public HashMap<String, String> getTestMap() {
return (HashMap<String, String>) testMap.clone();
}
// طريقة البناء التي تقوم بإجراء نسخ عميق
public FinalClassExample(int i, String n, HashMap<String,String> hm){
System.out.println("Performing Deep Copy for Object initialization");
// تشير "this" إلى الكائن الحالي
this.id=i;
this.name=n;
HashMap<String,String> tempMap=new HashMap<String,String>();
String key;
Iterator<String> it = hm.keySet().iterator();
while(it.hasNext()){
key=it.next();
tempMap.put(key, hm.get(key));
}
this.testMap=tempMap;
}
// اختبار الفئة الغير قابلة للتغيير
public static void main(String[] args) {
HashMap<String, String> h1 = new HashMap<String,String>();
h1.put("1", "first");
h1.put("2", "second");
String s = "original";
int i=10;
FinalClassExample ce = new FinalClassExample(i,s,h1);
// طباعة قيم ce
System.out.println("ce id: "+ce.getId());
System.out.println("ce name: "+ce.getName());
System.out.println("ce testMap: "+ce.getTestMap());
// تغيير قيم المتغيرات المحلية
i=20;
s="modified";
h1.put("3", "third");
// طباعة القيم مرة أخرى
System.out.println("ce id after local variable change: "+ce.getId());
System.out.println("ce name after local variable change: "+ce.getName());
System.out.println("ce testMap after local variable change: "+ce.getTestMap());
HashMap<String, String> hmTest = ce.getTestMap();
hmTest.put("4", "new");
System.out.println("ce testMap after changing variable from getter methods: "+ce.getTestMap());
}
}
قم بتجميع وتشغيل البرنامج:
- javac FinalClassExample.java
- java FinalClassExample
ملاحظة: قد تحصل على الرسالة التالية عند تجميع الملف: ملاحظة: يستخدم FinalClassExample.java عمليات غير متحققة أو غير آمنة
لأن دالة الحصول تستخدم تحويلًا غير متحقق من HashMap<String,String>
إلى Object
. يمكنك تجاهل تحذير المترجم لأغراض هذا المثال.
ستحصل على الناتج التالي:
OutputPerforming Deep Copy for Object initialization
ce id: 10
ce name: original
ce testMap: {1=first, 2=second}
ce id after local variable change: 10
ce name after local variable change: original
ce testMap after local variable change: {1=first, 2=second}
ce testMap after changing variable from getter methods: {1=first, 2=second}
يظهر الناتج أن قيم HashMap لم تتغير لأن البناء يستخدم نسخة عميقة ودالة الحصول تعيد نسخة من الكائن الأصلي.
ماذا يحدث عند عدم استخدام النسخ العميق والاستنساخ
يمكنك إجراء تغييرات في ملف FinalClassExample.java
لتظهر ما يحدث عند استخدام النسخ الضحل بدلاً من النسخ العميق وإعادة الكائن بدلاً من النسخة. يصبح الكائن غير غير قابل للتغيير. قم بإجراء التغييرات التالية في ملف المثال (أو انسخها والصقها من مثال الشيفرة):
- حذف طريقة البناء التي تقوم بتوفير النسخ العميق وإضافة طريقة البناء التي توفر النسخ الضحل المظللة كما هو موضح في المثال التالي.
- في وظيفة الحصول على القيمة، حذف
return (HashMap<String, String>) testMap.clone();
وإضافةreturn testMap;
.
يجب أن يبدو ملف المثال الآن كما يلي:
import java.util.HashMap;
import java.util.Iterator;
public final class FinalClassExample {
// حقول فئة FinalClassExample
private final int id;
private final String name;
private final HashMap<String,String> testMap;
public int getId() {
return id;
}
public String getName() {
return name;
}
// وظيفة الحصول على القيم للكائنات القابلة للتغيير
public HashMap<String, String> getTestMap() {
return testMap;
}
// طريقة البناء التي تقوم بنسخ الكائن ضحلة المظللة
public FinalClassExample(int i, String n, HashMap<String,String> hm){
System.out.println("Performing Shallow Copy for Object initialization");
this.id=i;
this.name=n;
this.testMap=hm;
}
// اختبار الفئة اللاقابلة للتغيير
public static void main(String[] args) {
HashMap<String, String> h1 = new HashMap<String,String>();
h1.put("1", "first");
h1.put("2", "second");
String s = "original";
int i=10;
FinalClassExample ce = new FinalClassExample(i,s,h1);
// طباعة قيم ce
System.out.println("ce id: "+ce.getId());
System.out.println("ce name: "+ce.getName());
System.out.println("ce testMap: "+ce.getTestMap());
// تغيير قيم المتغيرات المحلية
i=20;
s="modified";
h1.put("3", "third");
// طباعة القيم مرة أخرى
System.out.println("ce id after local variable change: "+ce.getId());
System.out.println("ce name after local variable change: "+ce.getName());
System.out.println("ce testMap after local variable change: "+ce.getTestMap());
HashMap<String, String> hmTest = ce.getTestMap();
hmTest.put("4", "new");
System.out.println("ce testMap after changing variable from getter methods: "+ce.getTestMap());
}
}
قم بتجميع البرنامج وتشغيله:
- javac FinalClassExample.java
- java FinalClassExample
OutputPerforming Shallow Copy for Object initialization
ce id: 10
ce name: original
ce testMap: {1=first, 2=second}
ce id after local variable change: 10
ce name after local variable change: original
ce testMap after local variable change: {1=first, 2=second, 3=third}
ce testMap after changing variable from getter methods: {1=first, 2=second, 3=third, 4=new}
تظهر الناتج أن قيم HashMap تغيرت لأن طريقة البناء تستخدم نسخًا غير عميقة، هناك إشارة مباشرة إلى الكائن الأصلي في دالة الحصول على القيمة.
الاستنتاج
لقد تعلمت بعض المبادئ العامة التي يجب اتباعها عند إنشاء فئات لا تتغير في جافا، بما في ذلك أهمية النسخ العميق. استمر في تعلمك مع المزيد من دروس جافا.
Source:
https://www.digitalocean.com/community/tutorials/how-to-create-immutable-class-in-java