Tại sao Java không cho phép ném ngoại lệ được kiểm tra từ khối khởi tạo tĩnh? Lý do đằng sau quyết định thiết kế này là gì?
Tại sao Java không cho phép ném ngoại lệ được kiểm tra từ khối khởi tạo tĩnh? Lý do đằng sau quyết định thiết kế này là gì?
Câu trả lời:
Bởi vì không thể xử lý các ngoại lệ được kiểm tra này trong nguồn của bạn. Bạn không có bất kỳ quyền kiểm soát nào đối với quá trình khởi tạo và các khối {} tĩnh có thể được gọi từ nguồn của bạn để bạn có thể bao quanh chúng bằng cách bắt thử.
Vì bạn không thể xử lý bất kỳ lỗi nào được chỉ ra bởi một ngoại lệ được kiểm tra, nên đã quyết định không cho phép ném các khối ngoại lệ được kiểm tra.
Các khối tĩnh không được ném kiểm tra ngoại lệ nhưng vẫn cho phép các ngoại lệ không được kiểm tra / thời gian chạy được ném. Nhưng theo những lý do trên, bạn sẽ không thể xử lý những điều này.
Tóm lại, hạn chế này ngăn cản (hoặc ít nhất là gây khó khăn hơn cho) nhà phát triển xây dựng một cái gì đó có thể dẫn đến lỗi mà ứng dụng không thể phục hồi.
static { if(1 < 10) { throw new NullPointerException(); } }
Bạn có thể giải quyết vấn đề bằng cách bắt bất kỳ ngoại lệ được kiểm tra nào và xem xét lại nó như một ngoại lệ không được kiểm tra. Lớp ngoại lệ không được kiểm tra này hoạt động tốt như một trình bao bọc : java.lang.ExceptionInInitializerError
.
Mã mẫu:
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) {
thay vào đó.
System.exit(...)
(hoặc tương đương) là lựa chọn duy nhất của bạn,
Nó sẽ phải trông như thế này (đây không phải là mã Java hợp lệ)
// Not a valid Java Code
static throws SomeCheckedException {
throw new SomeCheckedException();
}
Nhưng làm thế nào quảng cáo nơi bạn bắt nó? Kiểm tra ngoại lệ yêu cầu bắt. Tưởng tượng một số ví dụ có thể khởi tạo lớp (hoặc có thể không phải vì nó đã được khởi tạo) và để thu hút sự chú ý về độ phức tạp của nó sẽ giới thiệu, tôi đặt các ví dụ trong một bộ kích thích tĩnh khác:
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?
}
}
Và một điều khó chịu khác -
interface MyInterface {
final static ClassA a = new ClassA();
}
Hãy tưởng tượng ClassA có trình khởi tạo tĩnh ném một ngoại lệ được kiểm tra: Trong trường hợp này MyInterface (là giao diện với trình khởi tạo tĩnh 'ẩn') sẽ phải ném ngoại lệ hoặc xử lý ngoại lệ - xử lý ngoại lệ tại giao diện? Tốt hơn nên để nó như nó là.
main
có thể ném ngoại lệ được kiểm tra. Rõ ràng là những người không thể được xử lý.
main()
ngoại lệ với dấu vết ngăn xếp System.err
, sau đó gọi System.exit()
. Cuối cùng, câu trả lời cho câu hỏi này có lẽ là: "bởi vì các nhà thiết kế Java đã nói như vậy".
Tại sao Java không cho phép ném ngoại lệ được kiểm tra từ khối khởi tạo tĩnh?
Về mặt kỹ thuật, bạn có thể làm điều này. Tuy nhiên, ngoại lệ được kiểm tra phải được bắt trong khối. Một ngoại lệ được kiểm tra không được phép truyền ra khỏi khối.
Về mặt kỹ thuật, cũng có thể cho phép một ngoại lệ không được kiểm soát lan truyền ra khỏi khối khởi tạo tĩnh 1 . Nhưng đó là một ý tưởng thực sự tồi tệ để làm điều này một cách có chủ ý! Vấn đề là chính JVM đã bắt được ngoại lệ không được kiểm soát và kết thúc nó và truy xuất lại nó như một ExceptionInInitializerError
.
NB: đó Error
không phải là một ngoại lệ thông thường. Bạn không nên cố gắng phục hồi từ nó.
Trong hầu hết các trường hợp, ngoại lệ không thể bị bắt:
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)
Không có nơi nào bạn có thể đặt một try ... catch
ở trên để bắt ExceptionInInitializerError
2 .
Trong một số trường hợp bạn có thể bắt nó. Ví dụ, nếu bạn kích hoạt khởi tạo lớp bằng cách gọi Class.forName(...)
, bạn có thể gửi cuộc gọi trong một try
và bắt ExceptionInInitializerError
hoặc một hoặc sau đó NoClassDefFoundError
.
Tuy nhiên, nếu bạn cố gắng phục hồi từ một người ExceptionInInitializerError
bạn có khả năng gặp phải một rào cản. Vấn đề là trước khi đưa ra lỗi, JVM đánh dấu lớp gây ra sự cố là "thất bại". Bạn chỉ đơn giản là không thể sử dụng nó. Hơn nữa, bất kỳ lớp nào khác phụ thuộc vào lớp bị lỗi cũng sẽ chuyển sang trạng thái không thành công nếu chúng cố gắng khởi tạo. Cách duy nhất về phía trước là dỡ tất cả các lớp thất bại. Điều đó có thể khả thi đối với mã 3 được tải động , nhưng nói chung là không.
1 - Đó là lỗi biên dịch nếu một khối tĩnh vô điều kiện ném một ngoại lệ không được kiểm soát.
2 - Bạn có thể chặn nó bằng cách đăng ký một trình xử lý ngoại lệ chưa được mặc định, nhưng điều đó sẽ không cho phép bạn khôi phục, vì luồng "chính" của bạn không thể bắt đầu.
3 - Nếu bạn muốn khôi phục các lớp bị lỗi, bạn sẽ cần phải thoát khỏi trình nạp lớp đã tải chúng.
Lý do đằng sau quyết định thiết kế này là gì?
Đó là để bảo vệ các lập trình viên khỏi việc viết mã đưa ra các ngoại lệ không thể xử lý!
Như chúng ta đã thấy, một ngoại lệ trong trình khởi tạo tĩnh biến một ứng dụng điển hình thành cục gạch. Điều tốt nhất nghĩ rằng các nhà thiết kế ngôn ngữ có thể làm là xử lý trường hợp được kiểm tra là lỗi biên dịch. (Thật không may, nó cũng không thực tế để làm điều này cho các trường hợp ngoại lệ không được kiểm tra.)
OK, vậy bạn nên làm gì nếu mã của bạn "cần" để ném ngoại lệ vào trình khởi tạo tĩnh. Về cơ bản, có hai lựa chọn thay thế:
Nếu (đầy đủ!) Có thể khôi phục từ ngoại lệ trong khối, thì hãy làm điều đó.
Mặt khác, cơ cấu lại mã của bạn để việc khởi tạo không xảy ra trong khối khởi tạo tĩnh (hoặc trong bộ khởi tạo biến tĩnh).
Hãy xem Thông số kỹ thuật ngôn ngữ Java : thông báo rằng đó là lỗi thời gian biên dịch nếu trình khởi tạo tĩnh không thể hoàn thành đột ngột với một ngoại lệ được kiểm tra.
public class Main { static { try{Class.forName("whathappenswhenastaticblockthrowsanexception");} catch (ClassNotFoundException e){throw new RuntimeException(e);} } public static void main(String[] args){} }
Kết quả: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...
Vì không có mã nào bạn viết có thể gọi khối khởi tạo tĩnh, nên việc kiểm tra ném là không hữu ích exceptions
. Nếu có thể, jvm sẽ làm gì khi ném ngoại lệ được kiểm tra? Runtimeexceptions
được tuyên truyền lên.
Ví dụ: Spring 'DispatcherServlet (org.springframework.web.servlet.DispatcherServlet) xử lý tình huống bắt một ngoại lệ được kiểm tra và ném ra một ngoại lệ không được kiểm tra khác.
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());
}
Tôi có thể biên dịch ném Ngoại lệ được kiểm tra Ngoài ra ....
static {
try {
throw new IOException();
} catch (Exception e) {
// Do Something
}
}