הקומפיילר לא עומד בציפיות

תמונה של מתכנת עם לפטופ
קיימים מקרים שבהם היינו אולי מצפים לקומפיילר מעט חכם יותר אשר מסוגל להתריע על בעיות בקוד כבר בשלב הקומפילציה.

Share This Post

כאשר כותבים קוד בשפת התכנות Java אנו שומרים אותו בקבצים עם הסיומת Java. קבצים אלה עוברים קומפילציה, והתוצר של תהליך הקומפילציה הוא קבצים עם הסיומת class. כל type חדש שאנו מגדירים, בין אם מדובר בהגדרה של class, interface, annotation או record class, יישמר בקובץ נפרד. שמו של הקובץ הנפרד יהיה השם של ה-type שהוגדר, והסיומת שלו תהיה class. הקבצים עם הסיומת class כוללים בתוכם Java Byte Code אשר דומה לקוד בשפת אסמבלר. קבצי ה-Java Byte Code ירוצו באמצעות ה-Java Virtual Machine (ה-JVM). 

שגיאות קומפילציה ושגיאות בזמן ריצה

קיימות שגיאות רבות שיכולות לקרות בזמן הקומפילציה. שגיאות אלה כוללות בין היתר שגיאות תחביר (כתיבת קוד בניגוד לכללי השפה), שגיאות שנובעות מקוד שמנסה לבצע פעולה אשר מזוהה על ידי הקומפיילר כפעולה בלתי אפשרית (כגון קוד שמנסה להפעיל פונקציה או לגשת למשתנה שלא קיימים או שלא ניתן לגשת אליהם בגלל הרשאת הגישה המגבילה שיש להם), ושגיאות שמקורן בניסיון לבצע casting (המרה של ערך מ-type מסויים ל-type אחר שבאופן חד משמעי איננה אפשרית). במידה שהקומפילציה הצליחה יהיו ברשותנו קבצי Java Byte Code (קבצים עם הסיומת class) אשר נוכל לנסות להריץ. שגיאות בזמן ריצה מתרחשות בעת ההרצה של הקוד, ומקורן, בדרך כלל, בפעולה בלתי אפשרית שיש ניסיון לבצע אותה בזמן ריצה (לדוגמא, ניסיון לבצע casting ל-type של reference אשר איננו אפשרי). 

שגיאות בזמן קומפילציה אשר מקורן בניסיון לבצע casting

כאשר הקומפיילר מזהה ניסיון casting שבשום פנים ואופן איננו אפשרי הקומפיילר יתריע על כך, ואנו נקבל הודעת שגיאת קומפילציה. דוגמת הקוד להלן כוללת מספר classes. חלקם מקיימים קשרי הורשה. הניסיון לבצע casting יזוהה על ידי הקומפיילר כניסיון שבשום פנים ואופן איננו אפשרי, ועל כן אנו נקבל שגיאת קומפילציה. הקוד שלהלן לא יעבור קומפילציה. 

				
					class A {}

class B extends A {}

class C extends B {}

class D {}

public class Program {
    public static void main(String args[]) {
        D d = new D();
        A a = (A)d; //compilation error
    }
}
				
			

שורה 12 לא תעבור קומפילציה. הודעת השגיאה תהיה שלא ניתן לעשות casting של ה-type של ה-reference שבתוך המשתנה d מה-type ששמו D ל-type ששמו A. הקומפיילר מזהה casting זה כבעייתי כיוון שאין שום היתכנות לכך ש-reference מה-type ששמו A יהיה לאובייקט שנוצר מ-D. אילו D היה יורש מ-A אז הייתה היתכנות לביצוע ה-casting והקוד כן היה עובר קומפילציה בהצלחה. הקוד בדוגמה להלן יעבור קומפילציה בהצלחה. 

				
					class A {}

class B extends A {}

class C extends B {}

class D extends A {}

public class Program {
    public static void main(String args[]) {
        D d = new D();
        A a = (A)d;
    }
}
				
			

שורה 12 לא תעבור קומפילציה. הודעת השגיאה תהיה שלא ניתן לעשות casting של ה-type של ה-reference שבתוך המשתנה d מה-type ששמו D ל-type ששמו A. הקומפיילר מזהה casting זה כבעייתי כיוון שאין שום היתכנות לכך ש-reference מה-type ששמו A יהיה לאובייקט שנוצר מ-D. אילו D היה יורש מ-A אז הייתה היתכנות לביצוע ה-casting והקוד כן היה עובר קומפילציה בהצלחה. הקוד בדוגמה להלן יעבור קומפילציה בהצלחה. 

הקומפיילר מפתיע בכך שאיננו מצליח לזהות בעיה ב-casting

דוגמת הקוד הבאה יש ניסיון לבצע casting אשר בשלב הקומפילציה לא ניתן לזהות אותו כבעייתי ועל כן הוא יזוהה כבעייתי רק בשלב הריצה. הקוד להלן יעבור קומפילציה בהצלחה. כאשר ננסה להריץ אותו אז נקבל שגיאת ריצה. 

				
					class A {}

class B extends A {}

class C extends B {}

public class Program {
    public static void main(String args[]) {
        A a = new B();
        C c = (C)a;
    }
}
				
			

בשורה 10 מתבצע casting לערך שמוחזק בתוך המשתנה a. הערך שבתוך המשתנה a הוא reference מסוג A לאובייקט מסוג B. כאשר הקומפיילר מבצע קומפילציה לשורה 10 הוא מזהה את משתנה a כמשתנה אשר מחזיק ב-reference מסוג A לאובייקט שניתן לומר עליו שהוא גם מסוג A. ייתכן שהוא אובייקט שנוצר מ-A, ייתכן שנוצר מ-B וייתכן שנוצר מ-C. כיוון שהקומפיילר בשורה 10 לא יודע בוודאות מאיזה class האובייקט, ש-a מחזיק ב-reference שלו, נוצר, הקומפיילר לא יכול להתריע על בעיה והקוד עובר קומפילציה בהצלחה. כאשר הקוד ירוץ אנו נקבל שגיאה בזמן ריצה בשורה 10 כיוון שהמשתנה a מחזיק ב-reference לאובייקט שנוצר מ-B (לא מ-C). שגיאת הריצה תהיה ClassCastException. 

הדוגמא הבאה זהה לדוגמא הקודמת למעט הבדל אחד. במקום שבשורה 10 יבוצע casting לערכו של משתנה אשר אותחל בשורה אחרת, יש ניסיון לבצע casting ל-expression שמקבלים כאשר יוצרים אובייקט חדש באמצעות new. היינו מצפים שהקומפיילר יצליח לזהות שמדובר באובייקט שנוצר מ-B וייתן לנו שגיאת קומפילציה. בפועל, הקומפיילר לא מסוגל לזהות שמדובר באובייקט שנוצר מ-B. הקומפיילר מתייחס לכל ה-expression שמקבלים כשיוצרים אובייקט חדש, ()new B, בדיוק באותו אופן שהוא מתייחס ל-expression שמקבלים כאשר כותבים את שמו של משתנה, כפי שהיה בדוגמא הקודמת. כתוצאה מכך, הקוד עובר קומפילציה בהצלחה ורק בזמן ריצה מקבלים הודעת שגיאה. 

				
					class A {}

class B extends A {}

class C extends B {}

public class Program {
    public static void main(String args[]) {
        //A a = new B();
        C c = (C)(new B());
    }
}
				
			

האופן שבו פועל ה-compiler מזכיר במידה מסויימת את האופן שבו חמור מתהלך כשעיניו מכוסות בצדדיהן, כך שאיננו מסוגל לראות מעבר למה שמופיע בדיוק מולו. 

הירשמו לניוזלטר שלנו

התעדכנו בחידושים הטכנולוגים

פוסטים נוספים

תמונה של מתכנת עם לפטופ
Java

הקומפיילר לא עומד בציפיות

קיימים מקרים שבהם היינו אולי מצפים לקומפיילר מעט חכם יותר אשר מסוגל להתריע על בעיות בקוד כבר בשלב הקומפילציה.

הזנק את העסק שלך!

נשמח להיפגש לקפה!

life michael academy asynchronous online courses

Java | Python | JavaScript | TypeScript

Update cookies preferences