מתי ג'אווה זורקת את ExceptionInInitializerError?

1. סקירה כללית

במדריך מהיר זה, נראה מה גורם לג'אווה לזרוק מופע של ExceptionInInitializerError יוצא מן הכלל.

נתחיל במעט תיאוריה. אז נראה כמה דוגמאות לחריג זה בפועל.

2. ה ExceptionInInitializerError

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

למעשה, בכל פעם שחריג כלשהו מתרחש באתחול סטטי, Java עוטפת את החריג באופן אוטומטי במופע של ה- ExceptionInInitializerError מעמד. בדרך זו, היא גם שומרת על התייחסות לחריג בפועל כגורם השורש.

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

3. חסם אתחול סטטי

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

מעמד ציבורי StaticBlock {מצב פרטי סטטי פרטי; סטטי {state = 42/0; }}

עכשיו אם נפעיל את אתחול הכיתה במשהו כמו:

StaticBlock חדש ();

ואז נראה את החריג הבא:

java.lang.ExceptionInInitializerError at com.baeldung ... (ExceptionInInitializerErrorUnitTest.java:18) נגרם על ידי: java.lang.ArithmeticException: / על ידי אפס ב- com.baeldung.StaticBlock. (ExceptionInInitializerErrorUnitTest.java:35) ...

כפי שצוין קודם, ג'אווה זורקת את ExceptionInInitializerError חריג תוך שמירה על התייחסות לסיבה הבסיסית:

assertThatThrownBy (StaticBlock :: new) .isInstanceOf (ExceptionInInitializerError.class) .hasCauseInstanceOf (ArithmeticException.class);

ראוי גם להזכיר כי שיטה היא שיטת אתחול מחלקה ב- JVM.

4. אתחול משתנה סטטי

אותו דבר קורה אם Java לא מצליחה לאתחל משתנה סטטי:

מחלקה ציבורית StaticVar {state static int פרטי = initializeState (); private static int initializeState () {throw new RuntimeException (); }}

שוב, אם נפעיל את תהליך אתחול הכיתה:

StaticVar חדש ();

ואז מתרחש אותו חריג:

java.lang.ExceptionInInitializerError ב com.baeldung ... (ExceptionInInitializerErrorUnitTest.java:11) נגרם על ידי: java.lang.RuntimeException ב- com.baeldung.StaticVar.initializeState (ExceptionInInitializerErrorUnitTest.javaar. 26. at. ExceptionInInitializerErrorUnitTest.java:23) ... 23 נוספים

בדומה לבלוקים של אתחול סטטי, גם הסיבה הבסיסית לחריג נשמרת:

assertThatThrownBy (StaticVar :: new) .isInstanceOf (ExceptionInInitializerError.class) .hasCauseInstanceOf (RuntimeException.class);

5. חריגים מסומנים

כחלק ממפרט השפה של Java (JLS-11.2.3), איננו יכולים לזרוק חריגים מסומנים בתוך בלוק אתחול סטטי או אתחול משתנה סטטי. למשל, אם ננסה לעשות זאת:

מחלקה ציבורית NoChecked {static {throw new Exception (); }}

המהדר ייכשל עם שגיאת האוסף הבאה:

java: אתחול חייב להיות מסוגל להשלים כרגיל

כמוסכמת עלינו לעטוף את החריגים המסומנים האפשריים במופע של ExceptionInInitializerError כאשר לוגיקת האתחול הסטטית שלנו מביאה חריג מסומן:

מחלקה ציבורית CheckedConvention {בנאי קונסטרוקטור פרטי סטטי; סטטי {נסה {constructor = CheckedConvention.class.getDeclaredConstructor (); } לתפוס (NoSuchMethodException e) {לזרוק ExceptionInInitializerError חדש (e); }}}

כפי שמוצג לעיל, getDeclaredConstructor () שיטה זורקת חריג מסומן. לכן תפסנו את החריג המסומן ועטפנו אותו כפי שמציעה האמנה.

מכיוון שאנחנו כבר מחזירים מופע של ExceptionInInitializerError באופן חריג במפורש, ג'אווה לא תעטוף את החריג הזה בתוך עוד אחד ExceptionInInitializerError למשל.

עם זאת, אם אנו זורקים חריג אחר שאינו מסומן, ג'אווה תזרוק אחרת ExceptionInInitializerError:

סטטי {נסה {constructor = CheckedConvention.class.getConstructor (); } לתפוס (NoSuchMethodException e) {לזרוק RuntimeException (e) חדש; }}

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

java.lang.ExceptionInInitializerError ב com.baeldung.exceptionininitializererror ... נגרם על ידי: java.lang.RuntimeException: java.lang.NoSuchMethodException: ... נגרם על ידי: java.lang.NoSuchMethodException: com.baeldung.CheckedCon. java.base / java.lang.Class.getConstructor0 (Class.java:3427) ב- java.base / java.lang.Class.getConstructor (Class.java:2165)

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

5.1. OpenJDK

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

מחלקה ציבורית AtomicReference מיישמת java.io.Serializable {פרטית סופית VarHandle VALUE; סטטי {נסה {MethodHandles.Lookup l = MethodHandles.lookup (); VALUE = l.findVarHandle (AtomicReference.class, "value", Object.class); } לתפוס (ReflectiveOperationException e) {לזרוק ExceptionInInitializerError חדש (e); }} ערך V נדיף פרטי; // הושמט }

6. מסקנה

במדריך זה ראינו מה גורם לג'אווה לזרוק מופע של ה- ExceptionInInitializerError יוצא מן הכלל.

כרגיל, כל הדוגמאות זמינות ב- GitHub.