הקדמה
מאמר זה מספק סקירה כיצד ליצור מחלקה ללא תוקף בתכנות ב-Java.
אובייקט הוא לא ניתן לשינוי כאשר המצב שלו אינו משתנה לאחר שהוא אובייקט שנאתחל. לדוגמה, String
הוא מחלקה לא ניתנת לשינוי ולאחר השהייה, ערך של אובייקט מסוג String
אינו משתנה. למד עוד על למה מחלקת ה-String
היא לא ניתנת לשינוי ב-Java.
כיוון שאובייקט לא ניתן לשינוי, תוכניות צריכות ליצור אובייקט חדש לכל שינוי של מצב. עם זאת, לאובייקטים לא ניתנים לשינוי יש גם את היתרונות הבאים:
- מחלקה לא ניתנת לשינוי היא טובה למטרות של זיכרון מטמון מאחר ואין צורך לדאוג לשינויי ערך.
- מחלקה לא ניתנת לשינוי היא ללא תחרות טרדפים-בטוחה, כך שלא עליך לדאוג לביטחון טרדפים בסביבות רב-ליבה.
למד עוד על מרובי-ליבה ב-Java וסייר ב שאלות ראיון על מרובי-ליבה ב-Java.
יצירת מחלקה ללא אפשרות שינוי (Immutable) ב-Java
כדי ליצור מחלקה ללא אפשרות שינוי ב-Java, עליך לעקוב אחר העקרונות הכלליים הבאים:
- הגדר את המחלקה כ-
final
כך שלא תוכל להיות מורשת. - הגדר את כל השדות כ-
private
כדי שגישה ישירה לא תהיה מותרת. - אל תספק שיטות setter למשתנים.
- הגדר את כל השדות הניתנים לשינוי כ-
final
כך שערך של שדה יכול להיות מוקצה רק פעם אחת. - אתחל את כל השדות באמצעות שיטת בנאי שמבצעת העתק עמוק.
- בצע שכפול של אובייקטים בשיטות ה-getter כדי להחזיר העתק במקום להחזיר את האובייקט המקורי.
המחלקה הבאה היא דוגמה שמדגימה את היסודות של ללא אפשרות שינוי. המחלקה 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;
}
// פונקציית Getter עבור אובייקטים שינויים
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
הערה: ייתכן שתקבל את הודעת השגיאה הבאה בעת קידוד הקובץ: Note: FinalClassExample.java משתמש בפעולות שאינן בטוחות או לא מאומתות
מכיוון שפונקציית ה-Getter משתמשת בהמרת סוגים לא מאומתת מ-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 השתנו מכיוון ששיטת הבנאי משתמשת בהעתק רדוד ישנה התייחסות ישירה לאובייקט המקורי בפונקציית הגטר.
מסקנה
למדת כמה מהעקרונות הכלליים לעקוב אחריהם כאשר אתה יוצר מחלקות בלתי ניתנות לשינוי ב-Java, כולל החשיבות של העתק עמוק. המשך את למידתך עם עוד מדריכי Java.
Source:
https://www.digitalocean.com/community/tutorials/how-to-create-immutable-class-in-java