למה קיימים בנאים
בעמוד הקודם בניתם מחלקות ויצרתם אובייקטים עם new. בנאי הוא הקוד שבאמת רץ בזמן הקריאה ל-new. יש לו מטרה אחת: להחזיר אובייקט מוכן לשימוש, עם כל השדות הנדרשים מוגדרים, בלי מצב שאותחל רק בחצי.
בנאי נראה כמו מתודה, עם שני הבדלים: יש לו אותו שם כמו המחלקה, ואין לו טיפוס החזרה, אפילו לא void.
new Point(3, 4) מקצה את האובייקט ואז מריץ את גוף הבנאי עם x = 3 ו-y = 4. עד ש-new מחזיר, p מאותחל במלואו.
מילת המפתח this
בבנאי שבדוגמה, לפרמטרים קוראים x ו-y, בדיוק כמו לשדות. this.x אומר "השדה x ששייך לאובייקט הזה", ואילו x לבד מתייחס לפרמטר. בלי this, x = x היה פשוט משים את הפרמטר לעצמו ומשאיר את השדה בלי שינוי.
חייבים את this רק כשפרמטר מסתיר שדה, אבל הרבה אנשים משתמשים בו בכל מקום כדי שיהיה ברור. הטעות הנפוצה היא לשכוח אותו כשהשמות מתנגשים: הקוד מתקמפל, רץ, ומשאיר בשקט את השדות בערכי ברירת המחדל שלהם (null, 0, false).
בנאי ברירת המחדל
אם אף פעם לא כותבים בנאי, Java מספקת בשקט בנאי ברירת מחדל: בנאי public בלי ארגומנטים שלא עושה שום דבר נוסף. בגלל זה new עבד על מחלקות שלא היה להן בנאי בכלל.
המלכודת: ברגע שכותבים בנאי כלשהו, הבנאי החינמי נעלם.
class Box {
int size;
Box(int size) { // עכשיו אין בנאי בלי ארגומנטים
this.size = size;
}
}
new Box(); // שגיאת קומפילציה: לא קיים בנאי Box()
אם עדיין רוצים ש-new Box() יעבוד, הצהירו בעצמכם על הבנאי בלי הארגומנטים:
העמסת בנאים
למחלקה יכולים להיות כמה בנאים, כל עוד רשימות הפרמטרים שלהם שונות: זו פשוט העמסת מתודות (overloading) שמיושמת על בנאים. כל אחד מציע דרך אחרת לבנות את האובייקט.
Java בוחרת את הבנאי המתאים לפי מספר הארגומנטים שמעבירים ל-new והטיפוסים שלהם.
שרשור עם this()
שימו לב לכפילות בדוגמה הקודמת: כל בנאי משים את השדות בעצמו. אפשר להימנע מזה אם בנאי אחד קורא לבנאי אחר עם this(...). הקריאה חייבת להיות הפקודה הראשונה בבנאי.
עכשיו האתחול בפועל נמצא במקום אחד. הבנאים הקטנים רק משלימים ערכי ברירת מחדל ומעבירים את העבודה הלאה. אם תנסו לשים פקודה לפני this(...), הקומפיילר ידחה את זה.
בנאים ו-super()
כל בנאי קורא קודם כול, באופן מובלע, לבנאי של מחלקת העל שלו. אם לא כותבים כלום, Java מכניסה super() נסתר (הבנאי בלי הארגומנטים של מחלקת העל) בתחילת הגוף. תעבדו עם זה ישירות ברגע שתתחילו ליצור תתי-מחלקות, וזה הנושא הבא.
class Animal {
String name;
Animal(String name) { this.name = name; }
}
class Dog extends Animal {
Dog(String name) {
super(name); // כאן חובה לקרוא במפורש לבנאי של מחלקת האב
}
}
מכיוון שלמחלקה Animal אין בנאי בלי ארגומנטים, Dog חייבת לקרוא ל-super(name) במפורש: אין super() חינמי ליפול אליו. כמו this(), קריאה ל-super(...) חייבת להיות הפקודה הראשונה בבנאי.
הבא בתור: ירושה
בנאים מאתחלים אובייקט יחיד, אבל super() כבר רמז על משהו גדול יותר: מחלקות יכולות להיבנות על מחלקות אחרות ולהשתמש מחדש בשדות, במתודות ובבנאים שלהן. הקשר הזה, מחלקה אחת שמרחיבה אחרת, הוא ירושה, והוא העמוד הבא.
שאלות נפוצות
מה זה בנאי ב-Java?
בנאי (constructor) הוא מתודה מיוחדת שרצה כשיוצרים אובייקט עם new. יש לו בדיוק את השם של המחלקה ואין לו טיפוס החזרה (אפילו לא void). התפקיד שלו הוא להביא את האובייקט החדש למצב התחלתי תקין, בדרך כלל על ידי השמה של הארגומנטים של הבנאי לשדות של האובייקט.
מה ההבדל בין בנאי למתודה ב-Java?
לבנאי יש בדיוק את השם של המחלקה, הוא לא מצהיר על טיפוס החזרה, ואפשר להפעיל אותו רק עם new כשנוצר אובייקט. למתודה רגילה יש שם משלה, היא מצהירה על טיפוס החזרה (או void), וקוראים לה על אובייקט קיים. בנאים מאתחלים; מתודות עושות את העבודה אחר כך.
מה קורה אם לא כותבים בנאי ב-Java?
הקומפיילר נותן לכם בחינם בנאי ברירת מחדל בלי ארגומנטים, שלא מקבל פרמטרים ולא עושה כלום מעבר לקריאה המובלעת ל-super(). אבל ברגע שאתם כותבים בנאי כלשהו בעצמכם, הבנאי החינמי נעלם, כך שאם הוספתם בנאי עם פרמטרים ועדיין רוצים ש-new Thing() יעבוד, חייבים להצהיר במפורש על הבנאי בלי הארגומנטים.