שלב 1: שרשרת מתודות
חוק הברזל: קומפילציה קובעת העמסה, ריצה קובעת דריסה
הבנת פולימורפיזם בשפת Java (ושפות מונחות עצמים אחרות) מסתכמת בתהליך דו-שלבי ברור. כדי לדעת איזו מתודה תופעל בפועל, עלינו להפריד לחלוטין בין שלב הקומפילציה (ההידור) לשלב הריצה.
1 זמן קומפילציה: בחירת העמסה (Overload)
הקומפיילר עיוור למה שקורה בזמן ריצה. הוא מסתכל אך ורק על הטיפוסים הסטטיים (הטיפוסים דרכם הוגדרו המשתנים). מטרתו היא לנעול את "החתימה" המדויקת של המתודה.
- בודקים את הטיפוס הסטטי של המשתנה שמפעיל את המתודה.
- בודקים את הטיפוס הסטטי של הפרמטרים שהועברו אליה.
- מחפשים במחלקת האב את המתודה המתאימה ביותר.
2 זמן ריצה: בחירת דריסה (Override)
רק עכשיו, כשהתוכנית רצה והחתימה המדויקת כבר "נעולה", המחשב בודק מי הוא האובייקט האמיתי שנוצר בזיכרון.
- מסתכלים אך ורק על הטיפוס הדינמי של המשתנה שמפעיל את המתודה (למי עשינו
new). - הפרמטרים לא מעניינים יותר! הטיפוס הדינמי שלהם לא משפיע על בחירת המתודה ב-Java.
- בודקים: האם המחלקה של הטיפוס הדינמי דרסה את החתימה המדויקת שנבחרה בשלב 1?
דוגמת מעקב מלאה מהשטח
// C ו- D יורשות מ- A. כמו כן, B יורשת מ- A.
A myA = new D();
B myB = new B();
// מה יקרה כשנפעיל את השורה הבאה?
myA.f(myB);
מסתכלים על הטיפוס הסטטי של myA שהוא A. אנו קוראים למתודה f ומעבירים לה פרמטר שהטיפוס הסטטי שלו הוא B.
הקומפיילר הולך למחלקה A ומחפש מתודה f(B). הוא לא מוצא כזו! אבל הוא מוצא מתודה f(A) ומתודה f(int).
מכיוון ש-B יורש מ-A, מתבצע Upcasting אוטומטי לפרמטר, והקומפיילר נועל את החתימה: f(A).
עכשיו התוכנית רצה. מסתכלים על הטיפוס הדינמי של myA שהוא D.
הולכים למחלקה D ובודקים: האם המחלקה D דרסה את החתימה המדויקת f(A) שנבחרה בשלב הקודם?
אם כן — תופעל המתודה מתוך D.
אם לא — התוכנית תעלה במעלה עץ הירושה ותפעיל את המתודה המקורית מתוך A.