كيفية إنشاء صنف لا يمكن تغييره في جافا

المقدمة

هذا المقال يقدم نظرة عامة على كيفية إنشاء صنف لا يمكن تغييره في برمجة جافا.

الكائن غير قابل للتغيير عندما لا يتغير حالته بعد تهيئته. على سبيل المثال، String هو صنف لا يمكن تغييره و، بمجرد تحقيقه، لا يتغير قيمة كائن String أبدًا. تعرف على المزيد حول لماذا يكون صنف String لا يمكن تغييره في جافا.

نظرًا لعدم قدرة الكائن غير القابل للتغيير على التحديث، تحتاج البرامج إلى إنشاء كائن جديد لكل تغيير في الحالة. ومع ذلك، يحمل الكائنات غير القابلة للتغيير أيضًا الفوائد التالية:

  • الصنف اللا يمكن تغييره جيد لأغراض التخزين المؤقت لأنك لا تحتاج إلى القلق بشأن تغيير القيمة.
  • الصنف اللا يمكن تغييره آمن تلقائيًا مع الخيوط، لذا لا داعي للقلق بشأن سلامة الخيوط في بيئات متعددة الخيوط.

تعرف على المزيد حول التعددية في جافا وتصفح أسئلة مقابلات التعددية في جافا.

إنشاء صنف لا يمكن تغييره في لغة البرمجة جافا

لإنشاء صنف لا يمكن تغييره في لغة البرمجة جافا، يجب عليك اتباع هذه المبادئ العامة:

  1. أعلن الصنف كـ final لكي لا يمكن تمديده.
  2. اجعل جميع الحقول private حتى لا يُسمح بالوصول المباشر إليها.
  3. لا توفر أساليب تعيين (setter methods) للمتغيرات.
  4. اجعل جميع الحقول القابلة للتغيير (mutable fields) final حتى يمكن تعيين قيمة الحقل مرة واحدة فقط.
  5. قم بتهيئة جميع الحقول باستخدام أسلوب البناء (constructor) الذي يقوم بعملية النسخ العميق (deep copy).
  6. قم بعملية النسخ (cloning) للكائنات في أساليب الحصول (getter methods) لتعيين نسخة بدلاً من إرجاع مرجع الكائن الفعلي.

الصنف التالي هو مثال يوضح أساسيات عدم التغيير. يعرف صنف FinalClassExample الحقول ويوفر أسلوب البناء الذي يستخدم النسخ العميق لتهيئة الكائن. يقوم الكود في أسلوب main في ملف FinalClassExample.java باختبار عدم التغيير في الكائن.

أنشئ ملفًا جديدًا بالاسم 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());

	}

}

قم بتجميع وتشغيل البرنامج:

  1. javac FinalClassExample.java
  2. java FinalClassExample

ملاحظة: قد تحصل على الرسالة التالية عند تجميع الملف: ملاحظة: يستخدم FinalClassExample.java عمليات غير متحققة أو غير آمنة لأن دالة الحصول تستخدم تحويلًا غير متحقق من HashMap<String,String> إلى Object. يمكنك تجاهل تحذير المترجم لأغراض هذا المثال.

ستحصل على الناتج التالي:

Output
Performing 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;.

يجب أن يبدو ملف المثال الآن كما يلي:

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 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());

	}

}

قم بتجميع البرنامج وتشغيله:

  1. javac FinalClassExample.java
  2. java FinalClassExample

Output
Performing 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