Tại sao Java không cho phép ném ngoại lệ được kiểm tra khỏi khối khởi tạo tĩnh?


135

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ì?


Loại ngoại lệ nào bạn muốn đưa vào loại tình huống trong một khối tĩnh?
Kai Huppmann

1
Tôi không muốn làm bất cứ điều gì như thế. Tôi chỉ muốn biết tại sao bắt buộc phải bắt các ngoại lệ được kiểm tra bên trong khối tĩnh.
missingfaktor

Làm thế nào bạn mong đợi một ngoại lệ được kiểm tra sẽ được xử lý sau đó? Nếu điều đó làm phiền bạn, chỉ cần lấy lại ngoại lệ bị bắt bằng cách ném RuntimeException mới ("Tin nhắn kể chuyện", e);
Thorbjørn Ravn Andersen

18
@ ThorbjørnRavnAndersen Java thực sự cung cấp một loại Ngoại lệ cho tình huống đó: docs.oracle.com/javase/6/docs/api/java/lang/iêu
smp7d

@ smp7d Xem câu trả lời của kevinarpe bên dưới và nhận xét từ StephenC. Đó là một tính năng thực sự thú vị nhưng nó có bẫy!
Hết

Câu trả lời:


122

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.


69
Trên thực tế, câu trả lời này là không chính xác. Bạn CÓ THỂ ném ngoại lệ trong một khối tĩnh. Những gì bạn không thể làm là cho phép một ngoại lệ được kiểm tra lan truyền ra khỏi một khối tĩnh.
Stephen C

16
Bạn CÓ THỂ xử lý ngoại lệ này, nếu bạn đang tự tải lớp động, với Class.forName (..., true, ...); Cấp, đây không phải là một cái gì đó bạn đi qua rất thường xuyên.
LadyCailin

2
static {throw NullPulumExcpetion ()} mới - điều này cũng sẽ không được biên dịch!
Kirill Bazarov

4
@KirillBazarov một lớp có trình khởi tạo tĩnh luôn dẫn đến một ngoại lệ sẽ không biên dịch (vì tại sao lại như vậy?). Gói câu lệnh ném đó vào một mệnh đề if và bạn tốt để đi.
Kallja

2
@Ravisha vì trong trường hợp đó không có cơ hội cho trình khởi tạo hoàn thành bình thường trong mọi trường hợp. Với tính năng bắt thử, có thể không có ngoại lệ được in bởi println và do đó trình khởi tạo có cơ hội hoàn thành mà không có ngoại lệ. Đó là kết quả vô điều kiện của một ngoại lệ khiến nó trở thành lỗi biên dịch. Xem JLS để biết rằng: docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.7 Nhưng trình biên dịch vẫn có thể bị đánh lừa bằng cách thêm một điều kiện đơn giản trong trường hợp của bạn:static { if(1 < 10) { throw new NullPointerException(); } }
Kosi2801

67

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);
        }
    }
}

1
@DK: Có thể phiên bản Java của bạn không hỗ trợ loại mệnh đề bắt này. Hãy thử: catch (Exception e) {thay vào đó.
kevinarpe

4
Vâng, bạn có thể làm điều này, nhưng đó là một ý tưởng thực sự tồi tệ. Ngoại lệ không được kiểm tra đặt lớp và bất kỳ lớp nào khác phụ thuộc vào trạng thái không thành công mà chỉ có thể được giải quyết bằng cách dỡ bỏ các lớp. Điều đó thường là không thể, và System.exit(...)(hoặc tương đương) là lựa chọn duy nhất của bạn,
Stephen C

1
@StephenC chúng ta có thể nghĩ rằng nếu một lớp "cha mẹ" không tải được thì thực tế không cần thiết phải tải các lớp phụ thuộc của nó vì mã của bạn sẽ không hoạt động? Bạn có thể vui lòng cung cấp một số ví dụ về trường hợp cần phải tải một lớp phụ thuộc như vậy không? Cảm ơn
Ngày

Còn về ... nếu mã cố gắng tải nó một cách linh hoạt; ví dụ: thông qua Class.forName.
Stephen C

21

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à.


7
maincó thể ném ngoại lệ được kiểm tra. Rõ ràng là những người không thể được xử lý.
Ốc cơ khí

@M cơ học: Điểm thú vị. Trong mô hình tinh thần của Java, tôi giả sử rằng có một Thread.UncaughtExceptionHandler được gắn vào "luồng ma thuật" mặc định chạy 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".
kevinarpe

7

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: đó Errorkhô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 ExceptionInInitializerError2 .

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 tryvà bắt ExceptionInInitializerErrorhoặ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 ExceptionInInitializerErrorbạ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 đó 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 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ế:

  1. Nếu (đầy đủ!) Có thể khôi phục từ ngoại lệ trong khối, thì hãy làm điều đó.

  2. 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).


Có bất kỳ khuyến nghị chung nào về cách cấu trúc mã để nó không thực hiện bất kỳ khởi tạo tĩnh nào không?
MasterJoe2

Làm thế nào để những giải pháp âm thanh? stackoverflow.com/a/21321935/6648326stackoverflow.com/a/56575807/6648326
MasterJoe2

1
1) Tôi không có bất kỳ. 2) Chúng có vẻ tệ. Xem những bình luận tôi để lại trên chúng. Nhưng tôi chỉ nhắc lại những gì tôi đã nói trong Câu trả lời của tôi ở trên. Nếu bạn đọc và hiểu Câu trả lời của tôi, bạn sẽ biết những "giải pháp" đó không phải là giải pháp.
Stephen C

4

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.


5
Điều này không trả lời câu hỏi mặc dù. Ông hỏi tại sao đó là một lỗi thời gian biên dịch.
Winston Smith

Hmm, vì vậy việc ném bất kỳ RuntimeError nào là có thể, bởi vì JLS chỉ đề cập đến các ngoại lệ được kiểm tra.
Andreas Dolk

Điều đó đúng, nhưng bạn sẽ không bao giờ thấy là một stacktrace. Đó là lý do tại sao bạn cần cẩn thận với các khối khởi tạo tĩnh.
EJB

2
@EJB: Điều này không chính xác. Tôi vừa thử nó và đoạn mã sau đã cho tôi một ngăn xếp trực quan: 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...
Konrad Höffner

Phần "Nguyên nhân do" hiển thị dấu vết ngăn xếp mà bạn có thể quan tâm hơn.
LadyCailin

2

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.


1
Vâng, vâng tôi hiểu điều này bây giờ. Tôi đã rất ngớ ngẩn khi đăng một câu hỏi như thế này. Nhưng than ôi ... tôi không thể xóa nó bây giờ. :( Tuy nhiên, 1 trả lời của bạn ...
missingfaktor

1
@fast, Trên thực tế, các ngoại lệ được kiểm tra KHÔNG được chuyển đổi thành RuntimeExceptions. Nếu bạn tự viết mã byte, bạn có thể ném các ngoại lệ được kiểm tra bên trong trình khởi tạo tĩnh vào nội dung trái tim của bạn. JVM hoàn toàn không quan tâm đến việc kiểm tra ngoại lệ; nó hoàn toàn là một cấu trúc ngôn ngữ Java.
Antimon

0

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());
    }

1
Cách tiếp cận này là vấn đề mà ngoại lệ không được kiểm soát có thể bị bắt. Thay vào đó, nó đặt lớp và bất kỳ lớp nào khác phụ thuộc vào nó vào trạng thái không thể phục hồi.
Stephen C

@StephenC - Bạn có thể vui lòng cho một ví dụ đơn giản trong đó chúng tôi muốn có trạng thái có thể phục hồi không?
MasterJoe2

Theo giả thuyết ... nếu bạn muốn có thể khôi phục từ IOException để ứng dụng có thể tiếp tục. Nếu bạn muốn làm điều đó, thì bạn phải bắt ngoại lệ và thực sự xử lý nó ... không ném một ngoại lệ không được kiểm soát.
Stephen C

-5

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
    }
}

3
Vâng, nhưng bạn đang bắt nó trong khối tĩnh. Bạn không được phép ném một ngoại lệ được kiểm tra từ bên trong một khối tĩnh ra bên ngoài nó.
ArtOfWarfare
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.