ניסו סוף סוף סעיפים שהוגדרו והודגמו

ברוכים הבאים לפרק נוסף של Under The Hood . טור זה מעניק למפתחי ג'אווה הצצה למנגנונים המסתוריים שלוחצים ומסתחררים מתחת לתוכניות הג'אווה הרצות שלהם. מאמר החודש ממשיך את הדיון בערכת ההוראות של bytecode של המכונה הווירטואלית Java (JVM). המוקד שלה הוא האופן שבו ה- JVM מטפל finallyבסעיפים ובקודי הוואט הרלוונטיים לסעיפים אלה.

לסיום: משהו לעודד

מכיוון שהמכונה הווירטואלית של ג'אווה מבצעת את קודי הביץ המייצגים תוכנית ג'אווה, היא עשויה לצאת מגוש קוד - ההצהרות בין שני סוגריים מתולתלים תואמים - באחת מכמה דרכים. ראשית, ה- JVM יכול פשוט לבצע את הסוגר המתולתל הסוגר של גוש הקוד. לחלופין, הוא עלול להיתקל בהצהרת הפסקה, המשך או החזרה שגורמת לו לקפוץ מגוש הקוד מאיפה שהוא באמצע הבלוק. לבסוף, ניתן להשליך חריג הגורם ל- JVM לקפוץ לסעיף תפיסה תואם, או, אם אין סעיף תפיסה תואם, לסיים את השרשור. עם נקודות יציאה פוטנציאליות אלה הקיימות בתוך גוש קוד יחיד, רצוי שתהיה דרך קלה לבטא שמשהו קרה ולא משנה איך יוצאים מגוש קוד. בג'אווה, רצון כזה מתבטא עם atry-finally סָעִיף.

כדי להשתמש try-finallyבסעיף:

  • צרף tryבבלוק את הקוד שיש בו מספר נקודות יציאה, ו

  • הכניסו finallyלחסום את הקוד שחייב לקרות לא משנה איך tryיוצאים מהבלוק.

לדוגמה:

נסה {// חסימת קוד עם מספר נקודות יציאה} לבסוף {// חסימת קוד שמבוצעת תמיד כאשר יוצאת חסימת הניסיון, // לא משנה איך יוצאת חסימת הניסיון} 

אם יש לך catchסעיפים הקשורים tryלחסימה, עליך לשים את finallyהסעיף אחרי כל catchהסעיפים, כמו בסעיף:

נסה {// בלוק קוד עם מספר נקודות יציאה} לתפוס (קר ה) {System.out.println ("נתפס קר!"); } לתפוס (APopFly e) {System.out.println ("תפס זבוב פופ!"); } לתפוס (SomeonesEye e) {System.out.println ("תפס עין של מישהו!"); } לבסוף {// חסימת קוד שמבוצעת תמיד כשיוצאת בלוק הניסיון, // לא משנה איך יוצאים מחסימת הניסיון. System.out.println ("האם זה משהו לעודד?"); }

אם במהלך ביצוע הקוד בתוך tryבלוק, נזרק חריג שמטופל על ידי catchסעיף המשויך tryלבלוק, finallyהסעיף יבוצע לאחר catchהסעיף. לדוגמא, אם Coldנזרק חריג במהלך ביצוע ההצהרות (לא מוצג) tryבגוש לעיל, הטקסט הבא ייכתב לפלט הסטנדרטי:

נתפס קר! האם זה משהו לעודד?

נסה סוף סוף סעיפים בקודי בתים

בקודי בתים, finallyסעיפים משמשים כמתווכים זעירים בתוך שיטה. בכל נקודת יציאה בתוך tryבלוק catchוסעיפיו הנלווים אליו finallyנקרא תת-המוטיבציה המיניאטורית המתאימה לסעיף. לאחר finallyהשלמת הסעיף - כל עוד הוא מסתיים על ידי ביצוע ההצהרה האחרונה finallyבסעיף, לא על ידי השלכת חריג או ביצוע החזר, המשך או הפסקה - תת-המוטיבציה הזעירה עצמה חוזרת. הביצוע ממשיך רק מעבר לנקודה שבה נקראה תת-המוטיבציה המיניאטורית מלכתחילה, כך tryשניתן לצאת מהבלוק בצורה המתאימה.

האופקוד שגורם ל- JVM לקפוץ לתת-דרך מיניאטורית הוא ההוראה jsr . JSR ההוראה לוקחת האופרנד שני-בייט, קיזוז מהמיקום של JSR ההוראה שבו ששגרת מיניאטורי מתחילה. גרסה שנייה של הוראות jsr היא jsr_w , שממלא את אותה פונקציה כמו jsr אך לוקח אופרנד רחב (ארבעה בתים). כאשר ה- JVM נתקל בהוראות jsr או jsr_w , הוא דוחף כתובת חזרה אל הערימה, ואז ממשיך בביצוע בתחילת תת- הפעולה המיניאטורית. כתובת ההחזרה היא קיזוז קוד הביץ מיד לאחר ה- jsr אוהוראות jsr_w ומפעיליו .

לאחר השלמת תת-דרך מיניאטורית, היא קוראת להוראת ה- ret , אשר חוזרת מתת-הדרך. במיל ההוראה לוקחת האופרנד אחד, מדד לתוך המשתנים המקומיים שבם כתוב השולח מאוחסנת. קוד הצופן העוסק finallyבסעיפים מסוכם בטבלה הבאה:

סוף סוף סעיפים
אופקוד אופרנדים תיאור
jsr branchbyte1, branchbyte2 דוחף את כתובת החזרה, מסתעף לקיזוז
jsr_w branchbyte1, branchbyte2, branchbyte3, branchbyte4 דוחף את כתובת החזרה, מסתעף לקיזוז רחב
ret אינדקס חוזר לכתובת המאוחסנת באינדקס המשתנים המקומי

אל תבלבלו תת-סדרה מיניאטורית עם שיטת Java. שיטות Java משתמשות במערך הוראות אחר. הוראות כגון invokevirtual או invokenonvirtual הגורם שיטת Java כדי להיות מופעלת, והוראות כגון החזרה , areturn , או ireturn הגורם שיטת Java לחזור. JSR ההוראה אינה גורמת שיטת Java כדי להיות מופעלת. במקום זאת, זה גורם לקפיצה לאופקוד אחר בתוך אותה שיטה. כמו כן, הוראת ה- ret אינה חוזרת משיטה; במקום זאת, הוא חוזר לאופקוד באותה שיטה העוקבת מיד אחר הוראת ה- jsr הקוראת ואופרותיה . קודי ה- BYT שמיישמים אfinallyסעיף נקרא תת-דרך מיניאטורית מכיוון שהם מתנהגים כמו תת-דרך קטנה בתוך זרם ה- bytecode של שיטה אחת.

אתה עשוי לחשוב כי הוראת ה- ret צריכה להקפיץ את כתובת ההחזרה מהערימה, מכיוון ששם היא נדחפה על ידי הוראת ה- jsr . אבל זה לא. במקום זאת, בתחילה כל ששגרה, כתוב השולח הוא התפגרה העליון של המחסנית ומאוחסן במשתנה מקומית - אותו המשתנה המקומי שממנו במיל ההוראה מאוחר מקבל אותו. באופן סימטרי זה של עבודה עם כתובת השולח הוא הכרחי משום ולבסוף סעיפים (ולכן, שגרות מיניאטורי) עצמם יכולים לזרוק חריגים או לכלול return, breakאו continueהצהרות. בגלל אפשרות זו, כתובת ההחזרה הנוספת שנדחפה אל הערימה על ידי ה- jsrהוראה חייבת להיות מוסר מהערימה מייד, כדי שלא תהיה שם עדיין אם finallyיציאות סעיף עם break, continue, return, או חריג נזרק. לכן, כתובת ההחזרה נשמרת במשתנה מקומי בתחילת finallyתת-הפעולה המיניאטורית של כל סעיף.

כהמחשה, שקול את הקוד הבא, הכולל finallyסעיף שיוצא עם הצהרת הפסקה. התוצאה של קוד זה היא שללא קשר לפרמטר bVal שהועבר לשיטה surpriseTheProgrammer(), השיטה מחזירה false:

הפתעה בוליאנית סטטיתTheProgrammer (bVal boolean) {while (bVal) {try {return true; } סוף סוף {הפסקה; }} להחזיר שקר; }

הדוגמה לעיל מראה מדוע יש לאחסן את כתובת ההחזרה במשתנה מקומי בתחילת finallyהסעיף. מכיוון finallyשהסעיף יוצא עם הפסקה, הוא לעולם לא מבצע את הוראת ה- ret . כתוצאה מכך, ה- JVM לעולם לא חוזר לסיים את return trueההצהרה. במקום זאת, זה פשוט ממשיך עם הצניחה ועובר breakמעבר לסוגר המתולתל הסוגר של whileההצהרה. ההצהרה הבאה היא " return false," וזה בדיוק מה ש- JVM עושה.

ההתנהגות המוצגת על ידי finallyסעיף שיוצא עם a breakמוצגת גם על ידי finallyסעיפים היוצאים עם returnאו continue, או על ידי השלכת חריג. אם finallyיוצא סעיף מסיבה כלשהי, הוראת ה- ret בסוף finallyהסעיף לעולם אינה מתבצעת. מכיוון שלא מובטח שההוראה ret תבוצע, לא ניתן לסמוך עליה להסיר את כתובת ההחזרה מהערימה. לכן, כתובת ההחזרה נשמרת במשתנה מקומי בתחילת finallyתת-הפעולה המיניאטורית של הסעיף.

לדוגמא מלאה, שקול את השיטה הבאה, המכילה tryבלוק עם שתי נקודות יציאה. בדוגמה זו, שתי נקודות היציאה הן returnהצהרות:

static int giveMeThatOldFashionedBoolean (bVal boolean) {try {if (bVal) {return 1; } להחזיר 0; } סוף סוף {System.out.println ("התיישנת."); }}

השיטה שלעיל מקובצת לקודי הביצוע הבאים:

// רצף קוד הביצה של בלוק הניסיון: 0 iload_0 // דחוף משתנה מקומי 0 (arg עבר כמחלק) 1 ifeq 11 // דחוף משתנה מקומי 1 (arg עבר כדיבידנד) 4 iconst_1 // Push int 1 5 istore_3 // קפץ int (ה- 1), אחסן במשתנה מקומי 3 6 jsr 24 // קפוץ למיני תת-דרך לסעיף הסופי 9 iload_3 // דחוף את המשתנה המקומי 3 (1) 10 ireturn // החזר int על גבי ה- מחסנית (את 1) 11 iconst_0 // Push int 0 12 istore_3 // Pop int (the 0), חנות למשתנה מקומי 3 13 jsr 24 // קפיצה למיני תת-סעיף לסעיף הסופי 16 iload_3 // Push local משתנה 3 (ה- 0) 17 ireturn // החזר int על גבי הערימה (0) // רצף ה- bytecode עבור סעיף תפיסה שתופס כל סוג של חריג // שנזרק מתוך בלוק הניסיון. 18 astore_1 // פופ את ההתייחסות לחריג שנזרק,חנות // למשתנה מקומי 1 19 jsr 24 // קפצו למיני-תת-סעיף עבור הסעיף הסופי 22 aload_1 // דחפו את ההפניה (לחריג שנזרק) מ // משתנה מקומי 1 23 לזרוק // לזרוק מחדש את אותו החריג / / תת-הפעולה המיניאטורית המיישמת את החסימה הסופית. 24 astore_2 // קופץ לכתובת ההחזרה, אחסן אותה במשתנה מקומי 2 25 getstatic # 8 // קבל הפניה ל- java.lang.System.out 28 ldc # 1 // דחף מהבריכה הקבועה 30 invokevirtual # 7 // הפעל System.out.println () 33 ret 2 // חזור לכתובת החזרה המאוחסנת במשתנה המקומי 2אחסן אותו במשתנה מקומי 2 25 getstatic # 8 // קבל הפניה ל- java.lang.System.out 28 ldc # 1 // לדחוף מהבריכה הקבועה 30 invokevirtual # 7 // הפעל את System.out.println () 33 ret 2 // חזור לכתובת החזרה המאוחסנת במשתנה המקומי 2אחסן אותו במשתנה מקומי 2 25 getstatic # 8 // קבל הפניה ל- java.lang.System.out 28 ldc # 1 // לדחוף מהבריכה הקבועה 30 invokevirtual # 7 // הפעל את System.out.println () 33 ret 2 // חזור לכתובת החזרה המאוחסנת במשתנה המקומי 2

Bytecodes עבור tryהבלוק כולל שני JSR הוראות. הוראה נוספת של jsr כלולה catchבסעיף. catchהסעיף מתווסף ידי המהדר כי אם התרעה על חריגה במהלך הביצוע של tryשכונה, שכונה ולבסוף עדיין חייבת להיות מוצא להורג. לכן, catchהסעיף רק קורא לתת-המוטיבציה המיניאטורית שמייצגת את finallyהסעיף, ואז זורק שוב את אותו יוצא מן הכלל. טבלת החריגים של giveMeThatOldFashionedBoolean()השיטה, המוצגת להלן, מצביעה על כך שכל חריג שנזרק בין וכולל כתובות 0 ו -17 (כל קודי ה- BYT שמיישמים את tryהחסימה) מטופל על ידי catchהסעיף שמתחיל בכתובת 18.

טבלת חריגים: מבין סוג היעד 0 18 18 כלשהו 

קודי ה- Byte של finallyהסעיף מתחילים בהעלאת כתובת ההחזרה מהערימה ושמירה במשתנה מקומי שני. בסוף finallyהסעיף, הוראת ה- ret לוקחת את כתובת ההחזרה שלה מהמקום הנכון, משתנה מקומי שני.

HopAround: הדמיית מכונות וירטואליות של Java

היישומון שלהלן מדגים מכונה וירטואלית של Java המבצעת רצף של קודני byt. רצף ה- bytecode בסימולציה נוצר על ידי javacהמהדר עבור hopAround()שיטת המחלקה המוצגת להלן:

ליצן מחלקה {סטטי int hopAround () {int i = 0; בעוד (נכון) {נסה {נסה {i = 1; } לבסוף {// סוף סוף הראשון i = 2; } i = 3; להחזיר אני; // זה לעולם לא מסתיים, בגלל המשך} סוף סוף {// הסעיף השני סוף סוף אם (i == 3) {ממשיך; // המשך זה עוקף את הצהרת ההחזרה}}}}}

Bytecodes שנוצר על ידי javacעבור hopAround()השיטה מוצג להלן: