גוש אתחול
גוש אתחול (initialization block) הוא מושג בתכנות מונחה עצמים, הידוע בעיקר מ-Java, המהווה רצף של פקודות שתמבצעות בזמן יצירת (אתחול) מחלקות ואובייקטים. נועד להגדיל בצורה משמעותית את כוחו של הבנאי. קיימים שני סוגים: גוש אתחול סטטי, שבדרך כלל נקרא בקיצור גוש סטטי (static block), וגוש אתחול דינמי (instance block).
מוטיבציה
עריכהבזמן יצירת אובייקט מתבצעות פקודות שונות שנכתבות בבנאי. לפעמים יש צורך בהגדלת אפשרויות התחביר. בדרך כלל גוש דינמי קיים רק מטעמי נוחות - הוא ניתן להחלפה בקלות על ידי הוספת פונקציית אתחול וקריאתה מכל אחד מהבנאים. לעומת זאת, גוש סטטי מעלה בצורה משמעותית את הפונקציונליות של תוכנית, ולכן גם נמצא בשימוש נרחב הרבה יותר.
גוש אתחול סטטי
עריכהגוש סטטי מהווה למעשה בנאי למחלקה כולה, במקביל לבנאי הרגיל עבור אובייקט מסוים. התחביר הוא מהצורה
...
static {
// Static block code
}
...
המופיעה בין הגדרות שדות ופונקציות של המחלקה. הפקודות יבוצעו באחד משני המקרים, מה שיבוא קודם:
- יצירת אובייקט ראשון של המחלקה המדוברת במהלך זמן ריצה, לפני הפעלת הבנאי.
- קריאה ראשונה לפרוצדורה סטטית כל שהיא של המחלקה, לפני החישוב.
כלומר, הקוד מבוצע בפעם הראשונה בה המחלקה נטענת. בדוגמה המוצגת יש לכתוב מחלקה הממדלת מכוניות המיוצרות בחברה מסוימת ומלווה אותן לצורך מעקב למשך כל חייהן, כולל מצב בזמן נתון, בעלים, היסטוריית תיקונים וכדומה. כל אובייקט הוא מכונית, ובמחלקה עצמה ישנו שדה סטטי המכיל בסיס הנתונים של כל המכוניות. הבסיס הוא מסוג מיפוי, כשהמפתח הוא דגם המכונית, והתוכן הוא קבוצת המכוניות מדגם זה. הקוד הבא מדגים את השימוש בגוש אתחול סטטי:
public class Car {
static Map<String, Set<Car>> catalog;
static {
catalog = new HashMap<String, Set<Car>>();
catalog.put("model105", new HashSet<Car>());
catalog.put("model125", new HashSet<Car>());
catalog.put("model140", new HashSet<Car>());
catalog.put("model201", new HashSet<Car>());
}
public Car (String model) {
catalog.get(model).add(this);
// ...
}
// ...
}
את השורה 4 ניתן היה בקלות לצרף לשורה 2, ללא צורך בגוש אתחול. לעומת זאת, השורות 5-8 מראות את הצורך בו - אפשרות לבצע פקודות מורכבות ברמת המחלקה, שברמת האובייקט היו מופיעות בבנאי.
גוש אתחול דינמי
עריכהגוש דינמי מהווה תוספת לבנאים. התחביר הוא מהצורה
...
{
// Instance block code
}
...
המופיעה בין הגדרות שדות ופונקציות של האובייקטים במחלקה. הפקודות יבוצעו בזמן יצירת האובייקט. גוש אתחול דינמי מהווה תוספת לשיפור כתיבת הבנאי ולא מביא פונקציונליות נוספת. הוא מאפשר לחסוך יצירת פונקציית אתחול והוספת קריאה לכל הבנאים. לדוגמה, קטע הקוד:
public class Car {
static int count = 0;
public Car (String model) {
init();
// ...
}
public Car (String model, Double price) {
init();
// ...
}
private void init() {
count++;
System.out.println("Hello everyone, we have " + count + " cars now!");
}
// ...
}
שקול לקטע:
public class Car {
static int count = 0;
public Car (String model) {
// ...
}
public Car (String model, Double price) {
// ...
}
{
count++;
System.out.println("Hello everyone, we have " + count + " cars now!");
}
// ...
}
סדר פעולות ההטענה
עריכהבפיתוח Java נקבע סדר קבוע לפעולות ההטענה. בעת הטענת המחלקה הסדר הוא כדלקמן:
- הגדרת שדות סטטיים של מחלקות האב.
- אתחול שדות סטטיים וביצוע גושים סטטיים של מחלקות האב.
- הגדרת שדות סטטיים של המחלקה.
- אתחול שדות סטטיים וביצוע גושים סטטיים של המחלקה.
לאחר מכן, בעת יצירת האובייקט הסדר הוא כדלקמן:
- הגדרת שדות האובייקט של מחלקות האב.
- אתחול שדות האובייקט וביצוע גושים דינמיים של מחלקות האב.
- הפעלת בנאים של מחלקות האב.
- הגדרת שדות האובייקט של המחלקה.
- אתחול שדות האובייקט וביצוע גושים דינמיים של המחלקה.
- הפעלת בנאי המחלקה.
כאשר יש שרשרת של אבות קדמונים, כל הפעולות מבוצעות תחילה עבור האב הקדמון ביותר (מחלקת Object), ואז למטה לאורך השרשרת באותו אופן עד למחלקה הנוכחית.
כאשר יש יותר מסוג אחד באותו סעיף לעיל, הפעולות מתבצעות לפי סדר הופעתן. למשל, בקוד הבא:
public class T {
static int i = 5;
static {
i = 10;
}
static {
i = i * 3;
}
}
הערך של i בכל אובייקט שווה 30. אבל בקוד הזה:
public class T {
static {
i = 10;
}
static int i = 5;
static {
i = i * 3;
}
}
הערך הוא 15. כלומר, הגדרת השדה נעשית בהתחלה, ולאחר מכן כל הפעולות מבוצעות לפי סדר הופעתן בתוכנית - קודם הגוש הראשון, אז אתחול שדה, אז הגוש השני.
בעיות אפשריות
עריכהשימוש במשתנה טרם הגדרתו
עריכהבניגוד למה שניתן לצפות, הקוד הבא:
public class T {
static {
i = 5;
i = i + 1;
}
static int i = 5;
}
לא יתקמפל בשורה 4 בטענה כי במשתנה i הימני נעשה שימוש לפני שהוגדר, למרות ששורה 3 מתקמפלת ורצה בלי בעיות, למרות שה-i השמאלי בשורה 4 לא גורם לשגיאה, ולמרות שבזמן ריצה בהגעה לתחילת שורה 4 המשתנה הוגדר וקיבל ערך. זה קורה כי הצבת משתנים (למשל בשורה 3) נבדקת לפי רשימת המשתנים שהוגדרו באותו רגע מבחינת זמן ריצה, כולל כל השדות הסטטיים, והשימוש בערך המשתנה נבדק לפי מיקום הגדרתו.
משתנה סטטי מקומי
עריכהבניגוד למה שניתן לצפות, הקוד הבא:
public class T {
static {
int i = 10;
}
public static void main(String[] args) {
System.out.println(i);
}
}
לא יתקמפל בשורה 6 עקב משתנה לא מוגדר, כי הגדרת משתנה בגוש סטטי היא לא הגדרת משתנה סטטי, אלא משתנה מקומי באותו גוש בלבד. כלומר, הקוד
static {int i = 10;}
לא שקול לקוד
static int i = 10;
.