ברוכים הבאים לשיעור על ירושה ב-Java! ירושה היא אחד מעמודי התווך של תכנות מונחה עצמים (OOP), המאפשרת לנו לבנות היררכיות של מחלקות, לקדם שימוש חוזר בקוד (Code Reusability) ולשפר את גמישות המערכת. בשיעור זה נצלול לעומק מנגנון הירושה ב-Java, נבין את עקרונותיו, את הכלים שהוא מעמיד לרשותנו ואת שיקולי התכנון החשובים.
עקרונות הירושה ב-Java
מהי ירושה?
ירושה היא מנגנון שבו מחלקה אחת (מחלקה יורשת / Subclass / Child Class) יכולה לרשת תכונות (שדות) והתנהגויות (שיטות) ממחלקה אחרת (מחלקה מורישה / Superclass / Parent Class). הקשר בין המחלקות הוא קשר "is-a" (לדוגמה, "כלב הוא סוג של חיה"). מטרתה העיקרית היא שימוש חוזר בקוד וארגון לוגי של המחלקות.
מילת המפתח extends
ב-Java, אנו משתמשים במילת המפתח extends כדי להצהיר על ירושה. לדוגמה: class Dog extends Animal { ... }. מחלקה Dog תירש את כל השדות והשיטות הנגישים ממחלקת Animal.
ירושה יחידה
Java תומכת ב"ירושה יחידה" בלבד, כלומר, מחלקה יכולה לרשת רק ממחלקה אחת ישירות. עם זאת, היא יכולה לממש מספר ממשקים (Interfaces), כפי שנראה בהמשך.
מגדירי גישה (Access Modifiers)
מגדירי הגישה קובעים את נראותם של השדות והשיטות במחלקת האב עבור המחלקות היורשות:
public: נגיש לכולם.protected: נגיש למחלקות יורשות ולכל המחלקות באותה חבילה (package).default(ללא מילת מפתח): נגיש רק למחלקות באותה חבילה. אינו נגיש למחלקות יורשות בחבילות אחרות.private: אינו נגיש מחוץ למחלקה עצמה. מחלקות יורשות אינן יכולות לגשת ישירות לשדות ושיטותprivateשל מחלקת האב, אך הן יכולות להשתמש בהן בעקיפין דרך שיטותpublicאוprotectedשל האב.
רכיבי מפתח בירושה
מילת המפתח super
מילת המפתח super משמשת לשתי מטרות עיקריות:
- קריאה לבנאי של מחלקת האב: כאשר מחלקה יורשת יוצרת מופע, היא חייבת לאתחל גם את חלק האב שלה. קריאה ל-
super(...)בבנאי של מחלקת הבן מפעילה את הבנאי המתאים של מחלקת האב. קריאה זו חייבת להיות השורה הראשונה בבנאי של מחלקת הבן. - גישה לחברי מחלקת האב: ניתן להשתמש ב-
super.method()אוsuper.fieldכדי לגשת לשיטות או שדות של מחלקת האב, במיוחד כאשר הם עברו Overriding במחלקת הבן.
הבדלים קריטיים: Method Overriding vs. Method Overloading
Method Overriding
שיטה במחלקת הבן עם אותה חתימה (שם, סוגי פרמטרים וסדרם) כמו שיטה במחלקת האב. מאפשרת מימוש ספציפי למחלקת הבן תוך שמירה על ממשק אחיד. מתרחשת בזמן ריצה (Runtime Polymorphism).
Method Overloading
שיטות באותה מחלקה (או היררכיה) עם אותו שם אך חתימות שונות (מספר, סוג או סדר פרמטרים). מאפשרת לספק מספר דרכים לקרוא לפעולה בעלת שם דומה. מתרחשת בזמן קומפילציה.
פולימורפיזם (Polymorphism)
פולימורפיזם הוא היכולת של אובייקט לקבל צורות רבות. בהקשר של ירושה, הוא מתבטא ביכולת להתייחס לאובייקט של מחלקת בן כאל אובייקט של מחלקת אב (Upcasting), ובכך להפעיל עליו שיטות שמומשו במחלקת הבן (Overriding).
- Upcasting: המרה בטוחה ואוטומטית של אובייקט ממחלקת בן למחלקת אב. לדוגמה:
Animal a = new Dog();. הפניהaהיא מטיפוסAnimal, אך האובייקט בפועל הואDog. - Downcasting: המרה מפורשת של אובייקט ממחלקת אב למחלקת בן. לדוגמה:
Dog d = (Dog) a;. פעולה זו דורשת בדיקה (לרוב באמצעותinstanceof) ועלולה לגרום ל-ClassCastExceptionאם האובייקט בפועל אינו מהטיפוס שאליו מנסים להמיר.
מחלקה Object
כל המחלקות ב-Java יורשות ממחלקת Object באופן מרומז. היא מספקת שיטות בסיסיות כמו equals(), hashCode(), ו-toString(), שניתן לעשות להן Overriding כדי לספק התנהגות ספציפית למחלקה.
מחלקות אבסטרקטיות וממשקים
מחלקה אבסטרקטית (Abstract Class)
מחלקה אבסטרקטית היא מחלקה שלא ניתן ליצור ממנה מופעים ישירות. היא יכולה להכיל שיטות אבסטרקטיות (ללא מימוש, המסומנות ב-abstract) וגם שיטות רגילות עם מימוש. מחלקות בנות שיורשות ממחלקה אבסטרקטית חייבות לממש את כל השיטות האבסטרקטיות, אלא אם כן הן גם אבסטרקטיות בעצמן. מחלקות אבסטרקטיות מתאימות למקרים של "is-a" כאשר יש מימוש חלקי משותף, אך לא ניתן או לא הגיוני לממש את כל ההתנהגויות במחלקת האב.
ממשק (Interface)
ממשק מגדיר חוזה של התנהגויות (שיטות) ללא מימוש (בגרסאות Java מוקדמות). מחלקה יכולה לממש מספר ממשקים באמצעות מילת המפתח implements. ממשקים מייצגים קשר "can-do" או "has-a-capability" (לדוגמה, "כלב יכול לרוץ"). הם מאפשרים פולימורפיזם ללא ירושה ישירה של קוד, ומהווים דרך להשיג "ירושה מרובה של טיפוסים" ב-Java.
מחלקה אבסטרקטית
יכולה להכיל שדות, בנאים, שיטות ממומשות ואבסטרקטיות. מחלקה יורשת אחת בלבד. מתאימה להיררכיה עם התנהגות משותפת חלקית.
ממשק
מגדיר חוזה של שיטות (ללא מימוש בגרסאות מוקדמות). מחלקה יכולה לממש מספר ממשקים. מתאים להגדרת יכולות שונות ללא קשר היררכי ישיר.
שיקולי תכנון וטעויות נפוצות
ירושה מול קומפוזיציה ("is-a" vs. "has-a")
זוהי החלטת תכנון מרכזית. ירושה מתאימה כאשר יש קשר "is-a" מובהק. קומפוזיציה (Composition), שבה מחלקה "מכילה" אובייקטים של מחלקות אחרות כשדות (קשר "has-a"), עדיפה לעיתים קרובות לשמירה על גמישות וצמצום תלות. לדוגמה, "מכונית מכילה מנוע" (קומפוזיציה) עדיף על "מכונית היא סוג של מנוע" (ירושה).
מילת המפתח final
final class: מונעת ירושה מהמחלקה.final method: מונעת Overriding של השיטה במחלקות בנות.final field: הופך שדה לקבוע (ניתן לאתחול פעם אחת בלבד).
ClassCastException
שגיאה זו מתרחשת בזמן ריצה כאשר מנסים לבצע Downcasting לא חוקי, כלומר, להמיר אובייקט לטיפוס שאינו הטיפוס האמיתי שלו או טיפוס יורש שלו. לדוגמה, אם Animal a = new Cat();, ניסיון Dog d = (Dog) a; יזרוק ClassCastException.
שאלות לדיון
- הסבירו את הקשר "is-a" בהקשר של ירושה, ותנו דוגמה קונקרטית ב-Java. מדוע חשוב להקפיד על קשר זה בעת תכנון היררכיית מחלקות?
- מה ההבדל העיקרי בין Method Overriding ל-Method Overloading? מתי נשתמש בכל אחד מהם, ומדוע טעות נפוצה לבלבל ביניהם?
- הסבירו את עקרון הפולימורפיזם ב-Java ותנו דוגמה לקוד המדגים Upcasting ו-Downcasting. מה הסכנות הכרוכות ב-Downcasting?
- מתי עדיף להשתמש במחלקה אבסטרקטית ומתי בממשק? תארו תרחיש שבו כל אחד מהם יהיה הבחירה הנכונה.
- כיצד מילת המפתח
superתורמת למנגנון הירושה, ובאילו הקשרים היא הכרחית?
נקודות לתשובת מודל
- קשר "is-a": מחלקה יורשת היא "סוג של" מחלקה מורישה. לדוגמה,
Dog*is a*Animal. חשוב לשמור על עקביות לוגית למניעת בעיות תכנון ושימוש שגוי במנגנון הירושה. - Overriding vs. Overloading: Overriding משנה מימוש של שיטה קיימת במחלקת אב (אותה חתימה). Overloading יוצר שיטות חדשות עם אותו שם אך חתימות שונות. הראשון קשור לפולימורפיזם, השני לגמישות בקריאה. הבלבול נובע מהשם הדומה אך הפונקציונליות השונה לחלוטין.
- פולימורפיזם: היכולת להתייחס לאובייקט של מחלקת בן כאל אובייקט של מחלקת אב. Upcasting (
Parent p = new Child();) בטוח ואוטומטי. Downcasting (Child c = (Child) p;) דורש בדיקה (instanceof) ועלול לזרוקClassCastExceptionאם הטיפוס בפועל אינו תואם, מה שמוביל לקריסת התוכנית. - מחלקה אבסטרקטית מול ממשק: אבסטרקטית: קשר "is-a" חזק, יכולה להכיל מימוש חלקי, ירושה יחידה. מתאימה להיררכיה עם התנהגות משותפת חלקית. ממשק: חוזה התנהגות ("can-do"), ללא מימוש (במקור), ירושה מרובה של טיפוסים. מתאים להגדרת יכולות שונות שאינן קשורות בהכרח להיררכיה.
- שימוש ב-
super: לקריאה לבנאי של מחלקת האב (חובה כשהבנאי של האב אינו בנאי ברירת מחדל), ולגישה מפורשת לשדות או שיטות של מחלקת האב שייתכן ועברו Overriding במחלקת הבן.