เหตุใด Java จึงไม่อนุญาตให้โยนข้อยกเว้นที่ตรวจสอบจากบล็อกการเริ่มต้นแบบคงที่


136

เหตุใด Java จึงไม่อนุญาตให้โยนข้อยกเว้นที่ตรวจสอบแล้วจากบล็อกการเริ่มต้นแบบคงที่ อะไรคือเหตุผลที่อยู่เบื้องหลังการตัดสินใจออกแบบนี้


ข้อยกเว้นแบบใดที่คุณต้องการทิ้งสถานการณ์แบบใดในบล็อกคงที่?
Kai Huppmann

1
ฉันไม่อยากทำอะไรแบบนั้น ฉันแค่อยากรู้ว่าเหตุใดจึงจำเป็นต้องตรวจสอบข้อยกเว้นที่ตรวจสอบภายในบล็อกคงที่
missingfaktor

คุณคาดหวังว่าข้อยกเว้นที่ตรวจสอบแล้วจะได้รับการจัดการอย่างไร? หากมันรบกวนคุณเพียงแค่ลบข้อยกเว้นที่ถูกจับได้ใหม่โดยโยน RuntimeException ใหม่ ("การบอกข้อความ", จ);
Thorbjørn Ravn Andersen

18
@ ThorbjørnRavnAndersen Java มีประเภท Exception สำหรับสถานการณ์นั้นจริง: docs.oracle.com/javase/6/docs/api/java/lang/…
smp7d

@ smp7d ดูคำตอบของ kevinarpe ด้านล่างและความคิดเห็นจาก StephenC เป็นฟีเจอร์ที่เจ๋งมาก แต่มีกับดัก!
เบญจฯ

คำตอบ:


124

เนื่องจากไม่สามารถจัดการข้อยกเว้นที่ตรวจสอบเหล่านี้ในแหล่งที่มาของคุณได้ คุณไม่มีการควบคุมกระบวนการเริ่มต้นและไม่สามารถเรียกบล็อก {} แบบคงที่จากแหล่งที่มาของคุณเพื่อให้คุณสามารถล้อมรอบด้วย try-catch

เพราะคุณไม่สามารถจัดการกับข้อผิดพลาดใด ๆ ที่ระบุไว้โดยข้อยกเว้นการตรวจสอบก็ตัดสินใจที่จะขว้างปาไม่อนุญาตยกเว้นการตรวจสอบบล็อกแบบคงที่

บล็อกแบบคงที่ต้องไม่โยนข้อยกเว้นที่ตรวจสอบแล้วแต่ยังคงอนุญาตให้โยนข้อยกเว้นที่ไม่ได้ตรวจสอบ / รันไทม์ได้ แต่ด้วยเหตุผลข้างต้นคุณจะไม่สามารถจัดการกับสิ่งเหล่านี้ได้

สรุปได้ว่าข้อ จำกัด นี้ป้องกัน (หรืออย่างน้อยก็ทำให้ยากขึ้นสำหรับ) นักพัฒนาจากการสร้างบางสิ่งซึ่งอาจส่งผลให้เกิดข้อผิดพลาดซึ่งแอปพลิเคชันจะไม่สามารถกู้คืนได้


70
อันที่จริงคำตอบนี้ไม่ถูกต้อง คุณสามารถโยนข้อยกเว้นในบล็อกคงที่ สิ่งที่คุณไม่สามารถทำได้คืออนุญาตให้มีการตรวจสอบข้อยกเว้นเผยแพร่ออกจากบล็อกคงที่
Stephen C

16
คุณสามารถจัดการข้อยกเว้นนี้ได้หากคุณกำลังทำการโหลดคลาสแบบไดนามิกด้วยตัวเองด้วย Class.forName (... , true, ... ); จริงอยู่นี่ไม่ใช่สิ่งที่คุณพบบ่อยนัก
LadyCailin

2
คง {โยน NullPointerExcpetion ใหม่ ()} แบบคงที่ - สิ่งนี้จะไม่รวบรวมด้วย!
Kirill Bazarov

4
@KirillBazarov คลาสที่มีตัวเริ่มต้นแบบคงที่ซึ่งส่งผลให้เกิดข้อยกเว้นเสมอจะไม่คอมไพล์ (เพราะเหตุใดจึงควร) รวมคำสั่ง Throw นั้นไว้ใน if-clause และคุณก็พร้อมที่จะไป
Kallja

2
@Ravisha เพราะในกรณีนี้ไม่มีโอกาสที่ initializer จะทำงานได้ตามปกติไม่ว่าในกรณีใด ๆ ด้วยการลองจับอาจไม่มีข้อยกเว้นใด ๆ เกิดขึ้นจาก println ดังนั้น initializer จึงมีโอกาสที่จะดำเนินการให้เสร็จสมบูรณ์โดยไม่มีข้อยกเว้น เป็นผลลัพธ์ที่ไม่มีเงื่อนไขของข้อยกเว้นซึ่งทำให้เกิดข้อผิดพลาดในการคอมไพล์ ดู JLS สำหรับสิ่งนั้น: docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.7 แต่คอมไพเลอร์อาจยังหลงกลโดยการเพิ่มเงื่อนไขง่ายๆในกรณีของคุณ:static { if(1 < 10) { throw new NullPointerException(); } }
Kosi2801

67

คุณสามารถแก้ไขปัญหาได้โดยการจับข้อยกเว้นที่ตรวจสอบแล้วสร้างใหม่เป็นข้อยกเว้นที่ไม่ได้ตรวจสอบ java.lang.ExceptionInInitializerErrorระดับข้อยกเว้นนี้ไม่ถูกตรวจสอบทำงานได้ดีเป็นเสื้อคลุม:

โค้ดตัวอย่าง:

protected static class _YieldCurveConfigHelperSingleton {

    public static YieldCurveConfigHelper _staticInstance;

    static {
        try {
            _staticInstance = new YieldCurveConfigHelper();
        }
        catch (IOException | SAXException | JAXBException e) {
            throw new ExceptionInInitializerError(e);
        }
    }
}

1
@DK: Java เวอร์ชันของคุณอาจไม่รองรับคำสั่ง catch ประเภทนี้ ลอง: catch (Exception e) {แทน
kevinarpe

4
ใช่คุณสามารถทำได้ แต่เป็นความคิดที่แย่มาก ข้อยกเว้นที่ไม่ได้เลือกจะทำให้คลาสและคลาสอื่น ๆ ที่ขึ้นอยู่กับสถานะล้มเหลวซึ่งสามารถแก้ไขได้โดยการยกเลิกการโหลดคลาสเท่านั้น โดยทั่วไปจะเป็นไปไม่ได้และSystem.exit(...)(หรือเทียบเท่า) เป็นทางเลือกเดียวของคุณ
Stephen C

1
@StephenC เราคิดได้ไหมว่าถ้าไม่สามารถโหลดคลาส "พาเรนต์" ได้ก็ไม่จำเป็นต้องโหลดคลาสที่ขึ้นต่อกันเนื่องจากโค้ดของคุณไม่ทำงาน คุณช่วยยกตัวอย่างบางกรณีที่จำเป็นในการโหลดคลาสที่ขึ้นต่อกันได้หรือไม่ ขอบคุณ
Benj

ถ้าโค้ดพยายามโหลดแบบไดนามิก เช่นผ่าน Class.forName
Stephen C

21

มันจะต้องมีลักษณะเช่นนี้ (นี่ไม่ใช่รหัส Java ที่ถูกต้อง)

// Not a valid Java Code
static throws SomeCheckedException {
  throw new SomeCheckedException();
}

แต่คุณจะไปโฆษณาได้อย่างไร? ข้อยกเว้นที่ตรวจสอบแล้วจำเป็นต้องมีการจับ ลองนึกภาพตัวอย่างบางส่วนที่อาจเริ่มต้นคลาส (หรืออาจไม่ใช่เพราะเริ่มต้นแล้ว) และเพื่อดึงดูดความสนใจของความซับซ้อนของสิ่งนั้นที่จะแนะนำฉันใส่ตัวอย่างไว้ในตัวเหนี่ยวนำแบบคงที่อื่น:

static {
  try {
     ClassA a = new ClassA();
     Class<ClassB> clazz = Class.forName(ClassB.class);
     String something = ClassC.SOME_STATIC_FIELD;
  } catch (Exception oops) {
     // anybody knows which type might occur?
  }
}

และอีกสิ่งที่น่ารังเกียจ -

interface MyInterface {
  final static ClassA a = new ClassA();
}

ลองนึกภาพ ClassA มีตัวเริ่มต้นแบบคงที่ซึ่งส่งข้อยกเว้นที่ตรวจสอบแล้ว: ในกรณีนี้ MyInterface (ซึ่งเป็นอินเทอร์เฟซที่มีตัวเริ่มต้นแบบคงที่ 'ซ่อน') จะต้องทิ้งข้อยกเว้นหรือจัดการ - การจัดการข้อยกเว้นที่อินเทอร์เฟซ? ดีกว่าปล่อยให้มันเป็น


7
mainสามารถโยนข้อยกเว้นที่ตรวจสอบได้ แน่นอนว่าไม่สามารถจัดการได้
หอยทาก

@Mechanicalsnail: จุดที่น่าสนใจ ใน Java แบบจำลองทางจิตของฉันฉันคิดว่ามี Thread.UncaughtExceptionHandler ที่เชื่อมต่อกับเธรดที่รันmain()ซึ่งพิมพ์ข้อยกเว้นพร้อมการติดตามสแต็กไปยังSystem.errจากนั้นจึงเรียกSystem.exit()ใช้ ในท้ายที่สุดคำตอบของคำถามนี้น่าจะเป็น: "เพราะนักออกแบบ Java กล่าวเช่นนั้น"
kevinarpe

7

เหตุใด Java จึงไม่อนุญาตให้โยนข้อยกเว้นที่ตรวจสอบแล้วจากบล็อกการเริ่มต้นแบบคงที่

ในทางเทคนิคคุณสามารถทำได้ อย่างไรก็ตามข้อยกเว้นที่ตรวจสอบจะต้องถูกจับได้ภายในบล็อก ไม่อนุญาตให้มีการตรวจสอบข้อยกเว้นเพื่อเผยแพร่ออกจากบล็อก

ในทางเทคนิคก็ยังเป็นไปได้ที่จะอนุญาตให้มีข้อยกเว้นไม่ถูกตรวจสอบในการเผยแพร่ออกมาจากการเริ่มต้นคงกระชาก1 แต่มันเป็นความคิดที่แย่จริงๆที่จงใจทำแบบนี้! ปัญหาคือ JVM ตรวจจับข้อยกเว้นที่ไม่ได้ตรวจสอบและรวมเข้าและสร้างใหม่เป็นExceptionInInitializerError .

หมายเหตุ: นั่นคือไฟล์ Errorไม่ใช่ข้อยกเว้นปกติ คุณไม่ควรพยายามกู้คืนจากมัน

ในกรณีส่วนใหญ่จะไม่สามารถจับข้อยกเว้นได้:

public class Test {
    static {
        int i = 1;
        if (i == 1) {
            throw new RuntimeException("Bang!");
        }
    }

    public static void main(String[] args) {
        try {
            // stuff
        } catch (Throwable ex) {
            // This won't be executed.
            System.out.println("Caught " + ex);
        }
    }
}

$ java Test
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.RuntimeException: Bang!
    at Test.<clinit>(Test.java:5)

ไม่มีที่ไหนที่คุณสามารถวางtry ... catchด้านบนเพื่อจับExceptionInInitializerError2 2

ในบางกรณีคุณสามารถจับมันได้ ตัวอย่างเช่นถ้าคุณทริกเกอร์การเริ่มต้นคลาสโดยการโทรClass.forName(...)คุณสามารถปิดการโทรใน a tryและจับExceptionInInitializerErrorหรือตามมาNoClassDefFoundErrorหรือภายหลัง

อย่างไรก็ตามหากคุณพยายามที่จะกู้คืนจากExceptionInInitializerErrorคุณมีแนวโน้มที่จะพบสิ่งกีดขวางบนถนน ปัญหาคือก่อนที่จะโยนข้อผิดพลาด JVM จะทำเครื่องหมายคลาสที่ทำให้เกิดปัญหาว่า "ล้มเหลว" คุณจะไม่สามารถใช้งานได้ นอกจากนี้คลาสอื่น ๆ ที่ขึ้นอยู่กับคลาสที่ล้มเหลวจะเข้าสู่สถานะล้มเหลวด้วยหากพวกเขาพยายามเริ่มต้น วิธีเดียวที่จะไปข้างหน้าคือการยกเลิกการโหลดคลาสที่ล้มเหลวทั้งหมด ซึ่งอาจเป็นไปได้สำหรับโค้ด3 ที่โหลดแบบไดนามิกแต่โดยทั่วไปแล้วจะไม่เป็นเช่นนั้น

1 - เป็นข้อผิดพลาดในการคอมไพล์หากบล็อกแบบคงที่ส่งข้อยกเว้นที่ไม่ถูกตรวจสอบโดยไม่มีเงื่อนไข

2 - คุณอาจสกัดกั้นได้โดยการลงทะเบียนตัวจัดการข้อยกเว้นที่ไม่ถูกตรวจจับเริ่มต้น แต่จะไม่อนุญาตให้คุณกู้คืนเนื่องจากเธรด "หลัก" ของคุณไม่สามารถเริ่มทำงานได้

3 - หากคุณต้องการกู้คืนคลาสที่ล้มเหลวคุณจะต้องกำจัด classloader ที่โหลดคลาสเหล่านั้น


อะไรคือเหตุผลที่อยู่เบื้องหลังการตัดสินใจออกแบบนี้

เป็นการป้องกันโปรแกรมเมอร์เขียนโค้ดที่ทำให้เกิดข้อยกเว้นที่ไม่สามารถจัดการได้!

อย่างที่เราเห็นข้อยกเว้นในตัวเริ่มต้นแบบคงที่จะเปลี่ยนแอปพลิเคชันทั่วไปให้กลายเป็นอิฐ สิ่งที่ดีที่สุดที่นักออกแบบภาษาสามารถทำได้คือจัดการกับกรณีที่ตรวจสอบแล้วว่าเป็นข้อผิดพลาดในการคอมไพล์ (น่าเสียดายที่การทำเช่นนี้ไม่สามารถทำได้จริงสำหรับข้อยกเว้นที่ไม่ได้ตรวจสอบเช่นกัน)


ตกลงดังนั้นคุณควรทำอย่างไรหากโค้ดของคุณ "จำเป็น" เพื่อทิ้งข้อยกเว้นในตัวเริ่มต้นแบบคงที่ โดยทั่วไปมีสองทางเลือก:

  1. หากการกู้คืน (เต็ม!) จากข้อยกเว้นภายในบล็อกเป็นไปได้ให้ทำเช่นนั้น

  2. มิฉะนั้นให้ปรับโครงสร้างโค้ดของคุณเพื่อให้การเริ่มต้นไม่เกิดขึ้นในบล็อกการเริ่มต้นแบบคงที่ (หรือในตัวเริ่มต้นของตัวแปรคงที่)


มีคำแนะนำทั่วไปเกี่ยวกับวิธีการจัดโครงสร้างโค้ดเพื่อไม่ให้ทำการเริ่มต้นแบบคงที่หรือไม่?
MasterJoe

วิธีแก้ปัญหาเหล่านี้ฟังดูเป็นอย่างไร? stackoverflow.com/a/21321935/6648326และstackoverflow.com/a/56575807/6648326
MasterJoe

1
1) ฉันไม่มีเลย 2) ฟังดูไม่ดี ดูความคิดเห็นที่ฉันทิ้งไว้ แต่ฉันจะทำซ้ำสิ่งที่ฉันพูดในคำตอบด้านบนเท่านั้น หากคุณอ่านและเข้าใจคำตอบของฉันคุณจะรู้ว่า "วิธีแก้ปัญหา" เหล่านั้นไม่ใช่วิธีแก้ปัญหา
Stephen C

4

ดูข้อมูลจำเพาะของภาษา Java : ระบุว่าเป็นข้อผิดพลาดเวลาคอมไพล์หากตัวเริ่มต้นแบบคงที่ล้มเหลว สามารถดำเนินการได้ทันทีโดยมีข้อยกเว้นที่ตรวจสอบ


5
สิ่งนี้ไม่ตอบคำถามว่า เขาถามว่าเหตุใดจึงเป็นข้อผิดพลาดเวลาคอมไพล์
Winston Smith

อืมดังนั้นการโยน RuntimeError ควรเป็นไปได้เพราะ JLS กล่าวถึงเฉพาะข้อยกเว้นที่ตรวจสอบแล้ว
Andreas Dolk

ถูกต้อง แต่คุณจะไม่เห็นว่าเป็น stacktrace นั่นเป็นเหตุผลที่คุณต้องระวังบล็อคการเริ่มต้นแบบคงที่
EJB

2
@EJB: สิ่งนี้ไม่ถูกต้อง ฉันเพิ่งลองใช้และรหัสต่อไปนี้ทำให้ฉันเห็น stacktrace ภาพpublic class Main { static { try{Class.forName("whathappenswhenastaticblockthrowsanexception");} catch (ClassNotFoundException e){throw new RuntimeException(e);} } public static void main(String[] args){} }:Exception in thread "main" java.lang.ExceptionInInitializerError Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: whathappenswhenastaticblockthrowsanexception at Main.<clinit>(Main.java:6) Caused by: java.lang.ClassNotFoundException: whathappen...
Konrad Höffner

ส่วน "เกิดจาก" แสดงการติดตามสแต็กที่คุณอาจสนใจมากกว่า
LadyCailin

2

exceptionsตั้งแต่รหัสที่คุณเขียนไม่สามารถเรียกบล็อกเริ่มต้นคงที่ก็ไม่ได้เป็นประโยชน์ในการโยนการตรวจสอบ ถ้าเป็นไปได้ jvm จะทำอย่างไรเมื่อมีการโยนข้อยกเว้นที่ตรวจสอบแล้ว Runtimeexceptionsมีการขยายพันธุ์


1
ใช่ฉันเข้าใจสิ่งนี้แล้ว เป็นเรื่องโง่มากสำหรับฉันที่โพสต์คำถามแบบนี้ แต่อนิจจา ... ตอนนี้ยังลบไม่ได้ :( อย่างไรก็ตาม +1 สำหรับการตอบกลับของคุณ ...
missingfaktor

1
@fast จริงข้อยกเว้นที่ตรวจสอบแล้วจะไม่ถูกแปลงเป็น RuntimeExceptions หากคุณเขียน bytecode ด้วยตัวเองคุณสามารถโยนข้อยกเว้นที่ตรวจสอบไว้ภายในตัวเริ่มต้นแบบคงที่ไปยังเนื้อหาในใจของคุณได้ JVM ไม่สนใจเกี่ยวกับการตรวจสอบข้อยกเว้นเลย มันเป็นโครงสร้างภาษา Java ล้วนๆ
พลวง

0

ตัวอย่างเช่น: DispatcherServlet ของ Spring (org.springframework.web.servlet.DispatcherServlet) จัดการสถานการณ์ที่ตรวจจับข้อยกเว้นที่ตรวจสอบและโยนข้อยกเว้นอื่นที่ไม่ได้ตรวจสอบ

static {
    // Load default strategy implementations from properties file.
    // This is currently strictly internal and not meant to be customized
    // by application developers.
    try {
        ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
        defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    }
    catch (IOException ex) {
        throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
    }

1
วิธีนี้ทำให้เกิดปัญหาที่ไม่สามารถตรวจจับข้อยกเว้นที่ไม่ได้ตรวจสอบได้ แต่จะทำให้คลาสและคลาสอื่น ๆ ที่ขึ้นอยู่กับคลาสนั้นอยู่ในสถานะที่ไม่สามารถกู้คืนได้
Stephen C

@StephenC - คุณช่วยยกตัวอย่างง่ายๆที่เราต้องการให้รัฐกู้คืนได้ไหม?
MasterJoe

สมมุติว่า ... หากคุณต้องการกู้คืนจาก IOException เพื่อให้แอปพลิเคชันสามารถดำเนินการต่อได้ หากคุณต้องการทำเช่นนั้นคุณต้องจับข้อยกเว้นและจัดการกับมันจริงๆ ... อย่าโยนข้อยกเว้นที่ไม่ได้ตรวจสอบ
Stephen C

-5

ฉันสามารถรวบรวมการโยนข้อยกเว้นที่ถูกตรวจสอบได้ด้วย ....

static {
    try {
        throw new IOException();
    } catch (Exception e) {
         // Do Something
    }
}

3
ใช่ แต่คุณกำลังจับมันอยู่ในบล็อกคงที่ คุณไม่ได้รับอนุญาตให้โยนข้อยกเว้นที่ตรวจสอบแล้วจากภายในบล็อกคงที่ไปข้างนอก
ArtOfWarfare
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.