Ngoại lệ Java không bị bắt?


170

Tôi có một vấn đề lý thuyết nhỏ với các công trình thử bắt.

Tôi đã thực hiện một bài kiểm tra thực tế ngày hôm qua về Java và tôi không hiểu ví dụ sau:

try {
    try {
        System.out.print("A");
        throw new Exception("1");
    } catch (Exception e) {
        System.out.print("B");
        throw new Exception("2");
    } finally {
        System.out.print("C");
        throw new Exception("3");
    }
} catch (Exception e) {
    System.out.print(e.getMessage());
}

Câu hỏi là "đầu ra sẽ như thế nào?"

Tôi khá chắc chắn rằng đó sẽ là AB2C3, NHƯNG bất ngờ bất ngờ, điều đó không đúng.

Câu trả lời đúng là ABC3 (đã được thử nghiệm và thực sự là như vậy).

Câu hỏi của tôi là, Ngoại lệ ("2") đã đi đâu?


8
+1 Ahh, tôi biết câu trả lời này. Tôi đã được hỏi điều này trong một cuộc phỏng vấn. Đó là một câu hỏi rất hay để hiểu cách thử / bắt / cuối cùng hoạt động trên ngăn xếp.
Nhưng tôi không phải là một lớp bao bọc

10
Chỉ có một câu lệnh in có thể in một số (cuối cùng print(e.getMessage()):). Bạn nghĩ đầu ra sẽ là AB2C3: bạn có nghĩ catchkhối ngoài cùng sẽ được thực hiện hai lần không?
Adrian Pronk

Trong java, trước khi một lệnh chuyển điều khiển ra khỏi khối bắt được thực thi, khối cuối cùng được thực thi miễn là nó tồn tại. Nếu chỉ có mã trong khối cuối cùng không chuyển điều khiển ra bên ngoài, lệnh bị trì hoãn từ khối bắt được thực thi.
Thomas

Câu trả lời:


198

Từ Đặc tả ngôn ngữ Java 14.20.2. :

Nếu khối bắt hoàn thành đột ngột vì lý do R, thì khối cuối cùng được thực thi. Sau đó, có một sự lựa chọn:

  • Nếu khối cuối cùng hoàn thành bình thường, thì câu lệnh thử hoàn thành đột ngột vì lý do R.

  • Nếu khối cuối cùng hoàn thành đột ngột vì lý do S, thì câu lệnh thử hoàn thành đột ngột vì lý do S (và lý do R bị loại bỏ) .

Vì vậy, khi có một khối bắt ném một ngoại lệ:

try {
    // ...
} catch (Exception e) {
    throw new Exception("2");
}

nhưng cuối cùng cũng có một khối cũng ném ra một ngoại lệ:

} finally {
    throw new Exception("3");
}

Exception("2")sẽ bị loại bỏ và chỉ Exception("3")được tuyên truyền.


72
Điều này thậm chí đúng cho các returntuyên bố. Nếu khối cuối cùng của bạn có trả về, nó sẽ ghi đè bất kỳ trả về nào trong một tryhoặc catchkhối. Do những "tính năng" này, một thực tiễn tốt là cuối cùng, khối không bao giờ được ném ngoại lệ hoặc có tuyên bố trả lại.
Augusto

Đây cũng là lợi thế kế thừa mà tài nguyên thử có trong Java 7. Nó bảo tồn ngoại lệ ban đầu nếu một ngoại lệ thứ cấp được tạo khi đóng tài nguyên, thường làm cho việc gỡ lỗi dễ dàng hơn.
w25r

19

Các ngoại lệ được ném vào khối cuối cùng sẽ loại bỏ ngoại lệ được ném trước đó trong khối thử hoặc bắt.

Ví dụ về Java 7: http://ideone.com/0YdeZo

Từ ví dụ của Javadoc :


static String readFirstLineFromFileWithFinallyBlock(String path)
                                                     throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    } finally {
        if (br != null) br.close();
    }
}

Tuy nhiên, trong ví dụ này, nếu các phương thức readLine và đóng cả hai ngoại lệ, thì phương thức readFirstLineFromFileWithFinallyBlock sẽ ném ngoại lệ được ném từ khối cuối cùng; ngoại lệ được ném từ khối thử bị triệt tiêu.


try-withCú pháp mới của Java 7 bổ sung thêm một bước loại bỏ ngoại lệ: Các ngoại lệ được ném trong khối thử sẽ loại bỏ các lỗi được ném trước đó trong phần dùng thử.

từ cùng một ví dụ:

try (
        java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName);
        java.io.BufferedWriter writer = java.nio.file.Files.newBufferedWriter(outputFilePath, charset)
    ) {
        for (java.util.Enumeration entries = zf.entries(); entries.hasMoreElements();) {
            String newLine = System.getProperty("line.separator");
            String zipEntryName = ((java.util.zip.ZipEntry)entries.nextElement()).getName() + newLine;
            writer.write(zipEntryName, 0, zipEntryName.length());
        }
    }

Một ngoại lệ có thể được ném ra từ khối mã được liên kết với câu lệnh try-with-resource. Trong ví dụ trên, một ngoại lệ có thể được ném ra khỏi khối thử và có thể ném tối đa hai ngoại lệ từ câu lệnh try-with-resource khi nó cố gắng đóng các đối tượng ZipFile và BufferedWriter. Nếu một ngoại lệ được ném từ khối thử và một hoặc nhiều ngoại lệ được ném ra từ câu lệnh try-with-resource, thì những ngoại lệ đó được ném ra từ câu lệnh try-with-resource bị loại bỏ và ngoại lệ được ném bởi khối đó là ngoại lệ được ném bởi phương thức writeToFileZipFileContents. Bạn có thể truy xuất các ngoại lệ bị loại bỏ này bằng cách gọi phương thức throwable.getSuppressed từ ngoại lệ được ném bởi khối thử.


Trong mã từ câu hỏi, mỗi khối đều loại bỏ ngoại lệ cũ, thậm chí không đăng nhập nó, không tốt khi bạn đang cố gắng giải quyết một số lỗi:

http://en.wikipedia.org/wiki/Error_hiding


9

throw new Exception("2");được ném từ catchkhối và không try, nó sẽ không bị bắt lại.
Xem 14.20.2. Thực hiện thử cuối cùng và thử bắt cuối cùng .

Đây là những gì đang xảy ra:

try {
    try {
        System.out.print("A");         //Prints A
        throw new Exception("1");   
    } catch (Exception e) { 
        System.out.print("B");         //Caught from inner try, prints B
        throw new Exception("2");   
    } finally {
        System.out.print("C");         //Prints C (finally is always executed)
        throw new Exception("3");  
    }
} catch (Exception e) {
    System.out.print(e.getMessage());  //Prints 3 since see (very detailed) link
}

vâng đúng vậy, tôi thấy điều này đang xảy ra, nhưng tôi đang tìm lời giải thích - tại sao nó lại hành xử theo cách này
Kousalik

5

Câu hỏi của bạn rất rõ ràng và câu trả lời rất đơn giản ở cùng mức độ .. Đối tượng Ngoại lệ với thông báo là "2" được ghi đè bởi đối tượng Ngoại lệ với thông báo là "3".

Giải thích: Khi xảy ra Ngoại lệ, đối tượng của nó bị ném để bắt khối để xử lý. Nhưng khi ngoại lệ xảy ra trong chính khối bắt, đối tượng của nó được chuyển sang Khối OUTER CATCH (nếu có) để Xử lý ngoại lệ. Và điều tương tự đã xảy ra ở đây. Đối tượng ngoại lệ với thông báo "2" được chuyển sang Khối bắt OUTER. Nhưng hãy đợi .. Trước khi rời khỏi khối thử bắt bên trong, nó ĐÃ THỰC HIỆN CUỐI CÙNG. Ở đây xảy ra sự thay đổi mà chúng tôi quan tâm. Một đối tượng EXCEPTION mới (có thông báo "3") bị ném ra ngoài hoặc khối này cuối cùng đã thay thế đối tượng Exception đã ném (với thông báo "2"). Kết quả là, khi thông báo của đối tượng Exception được in, chúng tôi đã nhận được ghi đè giá trị tức là "3" chứ không phải "2".

Ghi nhớ: Chỉ có thể xử lý một đối tượng ngoại lệ trên khối CATCH.


2

Các finallykhối luôn luôn chạy. Hoặc bạn returntừ bên trong khối thử hoặc một ngoại lệ được ném ra. Ngoại lệ được ném trong finallykhối sẽ ghi đè lên một cú ném trong nhánh bắt.

Ngoài ra, việc ném một ngoại lệ sẽ không gây ra bất kỳ đầu ra nào. Dòng throw new Exception("2");này sẽ không viết bất cứ điều gì.


1
vâng, tôi biết tự ném đầu ra Exception, nhưng tôi không thấy lý do, tại sao Exception 2 nên bị loại bỏ. Tôi lại thông minh hơn một chút :-)
Kousalik

luôn luôn là thời gian rất dài và trong thời gian rất dài bất cứ điều gì cũng có thể xảy ra (kiểm tra câu đố wouter.coekaerts.be/2012/puheads-dreams )
Dainius

0

Theo mã của bạn:

try {
    try {
        System.out.print("A");
        throw new Exception("1");   // 1
    } catch (Exception e) {
        System.out.print("B");      // 2
        throw new Exception("2");
    } finally {                     // 3
        System.out.print("C");      // 4 
        throw new Exception("3");
    }
} catch (Exception e) {             // 5
    System.out.print(e.getMessage());
}

Như bạn có thể thấy ở đây:

  1. in A và ném ngoại lệ # 1;
  2. ngoại lệ này đã bị bắt bởi câu lệnh bắt và in B - # 2;
  3. khối cuối cùng # 3thực thi sau khi thử bắt (hoặc chỉ thử, nếu không xảy ra bất kỳ ngoại lệ nào) và in C - # 4và ném ngoại lệ mới;
  4. cái này đã bị bắt bởi tuyên bố bắt bên ngoài # 5;

Kết quả là ABC3. Và 2được bỏ qua cùng một cách với1


Xin lỗi, Ngoại lệ ("1") không bị bỏ qua, nhưng đã bị bắt thành công
Black Maggie

@Black Maggie Nó được lưu trữ và ném ngoại lệ mới => đây không phải là bộ nhớ cache và chương trình bị chấm dứt. Và trước khi khối này cuối cùng được thực thi.
nazar_art
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.