Ngoại lệ ném bên trong khối bắt - nó sẽ bị bắt lại?


180

Điều này có vẻ giống như một câu hỏi 101 lập trình và tôi đã nghĩ rằng tôi biết câu trả lời nhưng bây giờ thấy mình cần phải kiểm tra lại. Trong đoạn mã dưới đây, liệu ngoại lệ được ném trong khối bắt đầu tiên sau đó có bị bắt bởi khối bắt ngoại lệ chung bên dưới không?

try {
  // Do something
} catch(IOException e) {
  throw new ApplicationException("Problem connecting to server");
} catch(Exception e) {
  // Will the ApplicationException be caught here?
}

Tôi luôn nghĩ câu trả lời sẽ là không, nhưng bây giờ tôi có một số hành vi kỳ quặc có thể gây ra bởi việc này. Câu trả lời có lẽ giống nhau đối với hầu hết các ngôn ngữ nhưng tôi đang làm việc trong Java.


2
Có lẽ bạn có thể mô tả "hành vi kỳ quặc"?
Jeffrey L Whitledge

Bạn có chắc chắn ApplicationException không bị ném đi nơi khác và truyền đến khối này không?
sblundy

Tôi nhận thấy điều này trong IDE Eclipse của tôi. Việc buộc tôi phải đưa "Ngoại lệ mới" vào một khối thử nhưng tôi không biết tại sao. Tôi đã làm điều đó trong quá khứ mà không làm điều đó. Tôi không thấy lý do tại sao một khối thử sẽ được yêu cầu. Rất nhiều ví dụ trên Google cho thấy mọi người không cần một khối thử. Có phải vì tôi đang ném vào bên trong một câu lệnh if?
djangofan

Câu trả lời:


214

Không, vì cái mới throwkhông nằm trong trykhối trực tiếp.


Hãy nói nếu mã giống như thế này hãy thử {} Catch (Exception e) {System.err.println ("In Catch Exception:" + e.getClass ()); } Catch (IOException e) {System.err.println ("Đang bắt IOException:" + e.getClass ()); } và mã trong khối thử tạo IO Exception, nó sẽ chuyển sang khối Exception chung ngay lập tức hay nó sẽ bay qua khối bắt IOException?
sofs1

4
@ user3705478 Mã sẽ không được biên dịch, để tránh loại tình huống sai lầm đó. Nói chung, bạn không được phép bắt một lớp con sau khi bắt siêu lớp của nó trong cùng một khối thử.
Chris Jester-Young

Cảm ơn. Vì vậy, tôi sẽ nhận được một lỗi thời gian biên dịch, phải không? Tôi sẽ kiểm tra nó khi tôi về nhà.
sofs1

Điều đó phụ thuộc vào @ user3705478, nếu a RuntimeExceptionđược ném từ catchkhối sẽ không có lỗi biên dịch.
Randy the Dev

@AndrewDunn Tôi không nghĩ đó là câu hỏi của user3705478, nhưng thay vào đó, điều gì xảy ra nếu một catchmệnh đề ngoại lệ cha mẹ được liệt kê trước một catchmệnh đề ngoại lệ con . Sự hiểu biết của tôi là Java không cho phép điều này và nó bị bắt vào thời gian biên dịch.
Chris Jester-Young

71

Không. Rất dễ kiểm tra.

public class Catch {
    public static void main(String[] args) {
        try {
            throw new java.io.IOException();
        } catch (java.io.IOException exc) {
            System.err.println("In catch IOException: "+exc.getClass());
            throw new RuntimeException();
        } catch (Exception exc) {
            System.err.println("In catch Exception: "+exc.getClass());
        } finally {
            System.err.println("In finally");
        }
    }
}

Nên in:

Bắt IOException: class java.io.IOException
Vào lúc cuối
Ngoại lệ trong luồng "chính" java.lang.R nbException
        tại Catch.main (Catch.java:8)

Về mặt kỹ thuật có thể là một lỗi biên dịch, phụ thuộc vào việc thực hiện, hành vi không xác định hoặc một cái gì đó. Tuy nhiên, JLS được đóng đinh khá tốt và trình biên dịch đủ tốt cho loại điều đơn giản này (trường hợp góc chung có thể là một vấn đề khác).

Cũng lưu ý, nếu bạn trao đổi xung quanh hai khối bắt, nó sẽ không biên dịch. Bắt thứ hai sẽ hoàn toàn không thể truy cập.

Lưu ý rằng khối cuối cùng luôn chạy ngay cả khi khối bắt được thực thi (trừ các trường hợp ngớ ngẩn, chẳng hạn như các vòng lặp vô hạn, gắn qua giao diện công cụ và giết luồng, viết lại mã byte, v.v.).


3
Tất nhiên, cách rõ ràng nhất để tránh a finallylà gọi System.exit. :-P
Chris Jester-Young

3
@Chris Jester-Young for(;;);ngắn hơn, chứa trong ngôn ngữ, không giới thiệu nhiều về tác dụng phụ và, đối với tôi, rõ ràng hơn.
Tom Hawtin - tackline

1
System.exitthân thiện hơn với CPU! : -O Nhưng vâng, được thôi, rõ ràng đây là một tiêu chí chủ quan. Ngoài ra, tôi không biết bạn là một người chơi mã. ;-)
Chris Jester-Young

28

Đặc tả ngôn ngữ Java cho biết trong phần 14.19.1:

Nếu việc thực thi khối thử hoàn thành đột ngột vì ném giá trị V, thì có một lựa chọn:

  • Nếu loại thời gian chạy của V có thể gán cho Tham số của bất kỳ mệnh đề bắt nào của câu lệnh thử, thì mệnh đề bắt đầu tiên (ngoài cùng bên trái) như vậy được chọn. Giá trị V được gán cho tham số của mệnh đề bắt được chọn và Khối của mệnh đề bắt đó được thực thi. Nếu khối đó hoàn thành bình thường, thì câu lệnh thử hoàn thành bình thường; nếu khối đó hoàn thành đột ngột vì bất kỳ lý do nào, thì câu lệnh thử hoàn thành đột ngột vì lý do tương tự.

Tham khảo: http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#24134

Nói cách khác, sản phẩm khai thác kèm theo đầu tiên có thể xử lý ngoại lệ đó và nếu một ngoại lệ được loại bỏ khỏi sản phẩm khai thác đó, thì đó không nằm trong phạm vi của bất kỳ sản phẩm khai thác nào khác cho lần thử ban đầu, vì vậy họ sẽ không thử xử lý.

Một điều liên quan và khó hiểu cần biết là trong cấu trúc cuối cùng của [thử], một khối cuối cùng có thể ném ra một ngoại lệ và nếu vậy, bất kỳ ngoại lệ nào được ném bởi khối thử hoặc bắt bị mất. Điều đó có thể gây nhầm lẫn khi lần đầu tiên bạn nhìn thấy nó.


Chỉ muốn thêm rằng từ Java 7, bạn có thể tránh việc sử dụng trytài nguyên -with. Sau đó, nếu tryAND finallycả hai ném, finallybị triệt tiêu, nhưng cũng được thêm vào ngoại lệ try. Nếu catchném cũng vậy, bạn sẽ không gặp may, trừ khi bạn tự xử lý thông qua 'addSuppressed' và thêm tryngoại lệ - thì bạn có cả ba.
LAFK nói Phục hồi lại

6

Nếu bạn muốn ném một ngoại lệ từ khối bắt, bạn phải thông báo cho phương thức / lớp / v.v. rằng nó cần phải ném ngoại lệ nói. Thích như vậy:

public void doStuff() throws MyException {
    try {
        //Stuff
    } catch(StuffException e) {
        throw new MyException();
    }
}

Và bây giờ trình biên dịch của bạn sẽ không la mắng bạn :)


4

Không - Như Chris Jester-Young đã nói, nó sẽ được đưa lên lần thử tiếp theo trong hệ thống phân cấp.


2

Như đã nói ở trên ...
Tôi sẽ nói thêm rằng nếu bạn gặp khó khăn khi nhìn thấy những gì đang xảy ra, nếu bạn không thể tái tạo vấn đề trong trình gỡ lỗi, bạn có thể thêm một dấu vết trước khi ném lại ngoại lệ mới (với Hệ thống cũ tốt .out.println tệ hơn, với một hệ thống nhật ký tốt như log4j nếu không).


2

Nó sẽ không bị bắt bởi khối bắt thứ hai. Mỗi Ngoại lệ chỉ bị bắt khi ở trong khối thử. Bạn có thể lồng thử mặc dù (không phải đó là một ý tưởng tốt nói chung):

try {
    doSomething();
} catch (IOException) {
   try {
       doSomething();
   } catch (IOException e) {
       throw new ApplicationException("Failed twice at doSomething" +
       e.toString());
   }          
} catch (Exception e) {
}

Tôi đã viết một mã tương tự. Nhưng tôi không bị thuyết phục. Tôi muốn gọi một Thread.s ngủ () trong khối bắt của tôi. Nhưng Thread.s ngủ ném cho chính nó một Interrupttedception. Có đúng không (một cách thực hành tốt nhất) để làm điều đó như bạn đã thể hiện trong ví dụ của mình?
riroo

1

Không, vì tất cả các sản phẩm khai thác đều đề cập đến cùng một khối thử, do đó, việc ném từ bên trong một khối bắt sẽ bị bắt bởi một khối thử bao quanh (có thể trong phương thức gọi là khối này)


-4

Bài cũ nhưng biến "e" phải là duy nhất:

try {
  // Do something
} catch(IOException ioE) {
  throw new ApplicationException("Problem connecting to server");
} catch(Exception e) {
  // Will the ApplicationException be caught here?
}

5
Đây phải là một bình luận và không phải là một câu trả lời. Nó không cung cấp gì trong cách giải quyết câu hỏi thực tế - nó chỉ là một bài phê bình về câu hỏi của OP.
Derek W
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.