เหตุใด Java จึงไม่อนุญาตให้โยนข้อยกเว้นที่ตรวจสอบแล้วจากบล็อกการเริ่มต้นแบบคงที่ อะไรคือเหตุผลที่อยู่เบื้องหลังการตัดสินใจออกแบบนี้
เหตุใด Java จึงไม่อนุญาตให้โยนข้อยกเว้นที่ตรวจสอบแล้วจากบล็อกการเริ่มต้นแบบคงที่ อะไรคือเหตุผลที่อยู่เบื้องหลังการตัดสินใจออกแบบนี้
คำตอบ:
เนื่องจากไม่สามารถจัดการข้อยกเว้นที่ตรวจสอบเหล่านี้ในแหล่งที่มาของคุณได้ คุณไม่มีการควบคุมกระบวนการเริ่มต้นและไม่สามารถเรียกบล็อก {} แบบคงที่จากแหล่งที่มาของคุณเพื่อให้คุณสามารถล้อมรอบด้วย try-catch
เพราะคุณไม่สามารถจัดการกับข้อผิดพลาดใด ๆ ที่ระบุไว้โดยข้อยกเว้นการตรวจสอบก็ตัดสินใจที่จะขว้างปาไม่อนุญาตยกเว้นการตรวจสอบบล็อกแบบคงที่
บล็อกแบบคงที่ต้องไม่โยนข้อยกเว้นที่ตรวจสอบแล้วแต่ยังคงอนุญาตให้โยนข้อยกเว้นที่ไม่ได้ตรวจสอบ / รันไทม์ได้ แต่ด้วยเหตุผลข้างต้นคุณจะไม่สามารถจัดการกับสิ่งเหล่านี้ได้
สรุปได้ว่าข้อ จำกัด นี้ป้องกัน (หรืออย่างน้อยก็ทำให้ยากขึ้นสำหรับ) นักพัฒนาจากการสร้างบางสิ่งซึ่งอาจส่งผลให้เกิดข้อผิดพลาดซึ่งแอปพลิเคชันจะไม่สามารถกู้คืนได้
static { if(1 < 10) { throw new NullPointerException(); } }
คุณสามารถแก้ไขปัญหาได้โดยการจับข้อยกเว้นที่ตรวจสอบแล้วสร้างใหม่เป็นข้อยกเว้นที่ไม่ได้ตรวจสอบ 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);
}
}
}
catch (Exception e) {
แทน
System.exit(...)
(หรือเทียบเท่า) เป็นทางเลือกเดียวของคุณ
มันจะต้องมีลักษณะเช่นนี้ (นี่ไม่ใช่รหัส 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 (ซึ่งเป็นอินเทอร์เฟซที่มีตัวเริ่มต้นแบบคงที่ 'ซ่อน') จะต้องทิ้งข้อยกเว้นหรือจัดการ - การจัดการข้อยกเว้นที่อินเทอร์เฟซ? ดีกว่าปล่อยให้มันเป็น
main
สามารถโยนข้อยกเว้นที่ตรวจสอบได้ แน่นอนว่าไม่สามารถจัดการได้
main()
ซึ่งพิมพ์ข้อยกเว้นพร้อมการติดตามสแต็กไปยังSystem.err
จากนั้นจึงเรียกSystem.exit()
ใช้ ในท้ายที่สุดคำตอบของคำถามนี้น่าจะเป็น: "เพราะนักออกแบบ Java กล่าวเช่นนั้น"
เหตุใด 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
ด้านบนเพื่อจับExceptionInInitializerError
2 2
ในบางกรณีคุณสามารถจับมันได้ ตัวอย่างเช่นถ้าคุณทริกเกอร์การเริ่มต้นคลาสโดยการโทรClass.forName(...)
คุณสามารถปิดการโทรใน a try
และจับExceptionInInitializerError
หรือตามมาNoClassDefFoundError
หรือภายหลัง
อย่างไรก็ตามหากคุณพยายามที่จะกู้คืนจากExceptionInInitializerError
คุณมีแนวโน้มที่จะพบสิ่งกีดขวางบนถนน ปัญหาคือก่อนที่จะโยนข้อผิดพลาด JVM จะทำเครื่องหมายคลาสที่ทำให้เกิดปัญหาว่า "ล้มเหลว" คุณจะไม่สามารถใช้งานได้ นอกจากนี้คลาสอื่น ๆ ที่ขึ้นอยู่กับคลาสที่ล้มเหลวจะเข้าสู่สถานะล้มเหลวด้วยหากพวกเขาพยายามเริ่มต้น วิธีเดียวที่จะไปข้างหน้าคือการยกเลิกการโหลดคลาสที่ล้มเหลวทั้งหมด ซึ่งอาจเป็นไปได้สำหรับโค้ด3 ที่โหลดแบบไดนามิกแต่โดยทั่วไปแล้วจะไม่เป็นเช่นนั้น
1 - เป็นข้อผิดพลาดในการคอมไพล์หากบล็อกแบบคงที่ส่งข้อยกเว้นที่ไม่ถูกตรวจสอบโดยไม่มีเงื่อนไข
2 - คุณอาจสกัดกั้นได้โดยการลงทะเบียนตัวจัดการข้อยกเว้นที่ไม่ถูกตรวจจับเริ่มต้น แต่จะไม่อนุญาตให้คุณกู้คืนเนื่องจากเธรด "หลัก" ของคุณไม่สามารถเริ่มทำงานได้
3 - หากคุณต้องการกู้คืนคลาสที่ล้มเหลวคุณจะต้องกำจัด classloader ที่โหลดคลาสเหล่านั้น
อะไรคือเหตุผลที่อยู่เบื้องหลังการตัดสินใจออกแบบนี้
เป็นการป้องกันโปรแกรมเมอร์เขียนโค้ดที่ทำให้เกิดข้อยกเว้นที่ไม่สามารถจัดการได้!
อย่างที่เราเห็นข้อยกเว้นในตัวเริ่มต้นแบบคงที่จะเปลี่ยนแอปพลิเคชันทั่วไปให้กลายเป็นอิฐ สิ่งที่ดีที่สุดที่นักออกแบบภาษาสามารถทำได้คือจัดการกับกรณีที่ตรวจสอบแล้วว่าเป็นข้อผิดพลาดในการคอมไพล์ (น่าเสียดายที่การทำเช่นนี้ไม่สามารถทำได้จริงสำหรับข้อยกเว้นที่ไม่ได้ตรวจสอบเช่นกัน)
ตกลงดังนั้นคุณควรทำอย่างไรหากโค้ดของคุณ "จำเป็น" เพื่อทิ้งข้อยกเว้นในตัวเริ่มต้นแบบคงที่ โดยทั่วไปมีสองทางเลือก:
หากการกู้คืน (เต็ม!) จากข้อยกเว้นภายในบล็อกเป็นไปได้ให้ทำเช่นนั้น
มิฉะนั้นให้ปรับโครงสร้างโค้ดของคุณเพื่อให้การเริ่มต้นไม่เกิดขึ้นในบล็อกการเริ่มต้นแบบคงที่ (หรือในตัวเริ่มต้นของตัวแปรคงที่)
ดูข้อมูลจำเพาะของภาษา Java : ระบุว่าเป็นข้อผิดพลาดเวลาคอมไพล์หากตัวเริ่มต้นแบบคงที่ล้มเหลว สามารถดำเนินการได้ทันทีโดยมีข้อยกเว้นที่ตรวจสอบ
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...
exceptions
ตั้งแต่รหัสที่คุณเขียนไม่สามารถเรียกบล็อกเริ่มต้นคงที่ก็ไม่ได้เป็นประโยชน์ในการโยนการตรวจสอบ ถ้าเป็นไปได้ jvm จะทำอย่างไรเมื่อมีการโยนข้อยกเว้นที่ตรวจสอบแล้ว Runtimeexceptions
มีการขยายพันธุ์
ตัวอย่างเช่น: 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());
}
ฉันสามารถรวบรวมการโยนข้อยกเว้นที่ถูกตรวจสอบได้ด้วย ....
static {
try {
throw new IOException();
} catch (Exception e) {
// Do Something
}
}