ném Ngoại lệ vào các khối cuối cùng


100

Có một cách thanh lịch nào để xử lý các trường hợp ngoại lệ được đưa vào finallykhối không?

Ví dụ:

try {
  // Use the resource.
}
catch( Exception ex ) {
  // Problem with the resource.
}
finally {
   try{
     resource.close();
   }
   catch( Exception ex ) {
     // Could not close the resource?
   }
}

Làm thế nào để bạn tránh try/ catchtrong finallykhối?

Câu trả lời:


72

Tôi thường làm như thế này:

try {
  // Use the resource.
} catch( Exception ex ) {
  // Problem with the resource.
} finally {
  // Put away the resource.
  closeQuietly( resource );
}

Ở những nơi khác:

protected void closeQuietly( Resource resource ) {
  try {
    if (resource != null) {
      resource.close();
    }
  } catch( Exception ex ) {
    log( "Exception during Resource.close()", ex );
  }
}

4
Yeap, tôi sử dụng một thành ngữ rất giống. Nhưng tôi không tạo một chức năng cho điều đó.
OscarRyz

9
Một hàm rất hữu ích nếu bạn cần sử dụng thành ngữ ở một vài nơi trong cùng một lớp.
Darron

Việc kiểm tra null là thừa. Nếu tài nguyên là null, thì phương thức gọi bị hỏng nên được sửa. Ngoài ra, nếu tài nguyên trống, điều đó có thể phải được ghi lại. Nếu không, nó dẫn đến một ngoại lệ tiềm năng bị bỏ qua một cách âm thầm.
Dave Jarvis

14
Việc kiểm tra null không phải lúc nào cũng thừa. Hãy nghĩ về "resource = new FileInputStream (" file.txt ")" là dòng đầu tiên của quá trình thử. Ngoài ra, câu hỏi này không phải về lập trình định hướng khía cạnh mà nhiều người không sử dụng. Tuy nhiên, khái niệm rằng Ngoại lệ không nên bị bỏ qua đã được xử lý gọn gàng nhất bằng cách hiển thị một câu lệnh nhật ký.
Darron

1
Resource=> Closeable?
Dmitry Ginzburg

25

Tôi thường sử dụng một trong các closeQuietlyphương pháp trong org.apache.commons.io.IOUtils:

public static void closeQuietly(OutputStream output) {
    try {
        if (output != null) {
            output.close();
        }
    } catch (IOException ioe) {
        // ignore
    }
}

3
Bạn có thể thực hiện phương pháp này tổng quát hơn với thể đóng khoảng trống public static closeQuietly (có thể đóng closeable) {
Peter Lawrey

6
Có, đóng cửa là tốt đẹp. Thật tiếc khi nhiều thứ (như tài nguyên JDBC) không triển khai nó.
Darron

22

Nếu bạn đang sử dụng Java 7 và resourcecài đặt AutoClosable, bạn có thể làm điều này (sử dụng InputStream làm ví dụ):

try (InputStream resource = getInputStream()) {
  // Use the resource.
}
catch( Exception ex ) {
  // Problem with the resource.
}

8

Có thể là hơi quá, nhưng có thể hữu ích nếu bạn đang để các ngoại lệ bong bóng và bạn không thể ghi lại bất kỳ thứ gì từ bên trong phương thức của mình (ví dụ: vì đó là một thư viện và bạn muốn để mã gọi xử lý các ngoại lệ và ghi nhật ký):

Resource resource = null;
boolean isSuccess = false;
try {
    resource = Resource.create();
    resource.use();
    // Following line will only run if nothing above threw an exception.
    isSuccess = true;
} finally {
    if (resource != null) {
        if (isSuccess) {
            // let close throw the exception so it isn't swallowed.
            resource.close();
        } else {
            try {
                resource.close();
            } catch (ResourceException ignore) {
                // Just swallow this one because you don't want it 
                // to replace the one that came first (thrown above).
            }
        }
    }
}

CẬP NHẬT: Tôi đã xem xét vấn đề này nhiều hơn một chút và tìm thấy một bài đăng trên blog tuyệt vời từ một người có suy nghĩ rõ ràng về điều này hơn tôi: http://illegalargumentexception.blogspot.com/2008/10/java-how-not-to-make -mess-of-stream.html Anh ấy tiến thêm một bước nữa và kết hợp hai ngoại lệ thành một, điều này tôi có thể thấy hữu ích trong một số trường hợp.


1
+1 cho liên kết blog. Ngoài ra, ít nhất tôi sẽ ghi lại ignorengoại lệ
Denis Kniazhev.

6

Kể từ Java 7, bạn không còn cần phải đóng tài nguyên một cách rõ ràng trong một khối cuối cùng thay vào đó bạn có thể sử dụng cú pháp try -with-resources. Câu lệnh try-with-resources là câu lệnh try khai báo một hoặc nhiều tài nguyên. Tài nguyên là một đối tượng phải được đóng lại sau khi chương trình kết thúc với nó. Câu lệnh try-with-resources đảm bảo rằng mỗi tài nguyên được đóng ở cuối câu lệnh. Bất kỳ đối tượng nào triển khai java.lang.AutoClosable, bao gồm tất cả các đối tượng triển khai java.io.Closable, đều có thể được sử dụng làm tài nguyên.

Giả sử đoạn mã sau:

try( Connection con = null;
     Statement stmt = con.createStatement();
     Result rs= stmt.executeQuery(QUERY);)
{  
     count = rs.getInt(1);
}

Nếu có bất kỳ ngoại lệ nào xảy ra, phương thức đóng sẽ được gọi trên mỗi tài nguyên trong số ba tài nguyên này theo thứ tự ngược lại mà chúng được tạo. Nó có nghĩa là phương thức close sẽ được gọi đầu tiên cho ResultSetm, sau đó là Statement và cuối cùng cho đối tượng Connection.

Cũng cần biết rằng bất kỳ ngoại lệ nào xảy ra khi các phương thức đóng tự động được gọi đều bị loại bỏ. Các ngoại lệ bị loại bỏ này có thể được truy xuất bằng phương thức getsuppressed () được định nghĩa trong lớp Throwable .

Nguồn: https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html


Có vẻ như không đầy đủ rằng câu trả lời này không đề cập đến sự khác biệt về hành vi giữa cách tiếp cận này và cách mã ví dụ được đăng của OP hoạt động.
Nathan Hughes

2
sử dụng try-with-resources ném một ngoại lệ khi đóng nếu phần trong khối try hoàn thành bình thường nhưng phương thức close thì không, không giống như những gì mã OP thực hiện. đề xuất nó như một sự thay thế mà không thừa nhận sự thay đổi trong hành vi dường như có khả năng gây hiểu lầm.
Nathan Hughes

Nó không ném ra một ngoại lệ, phương thức đóng tự động được gọi sẽ bị loại bỏ.
Soroosh

2
hãy thử trường hợp tôi đã mô tả. thử khối hoàn thành bình thường, đóng ném cái gì đó. và đọc lại trang mà bạn đã đăng liên kết đến, việc ngăn chặn chỉ áp dụng khi khối thử ném thứ gì đó.
Nathan Hughes

3

Bỏ qua các ngoại lệ xảy ra trong khối 'cuối cùng' nói chung là một ý tưởng tồi trừ khi người ta biết những ngoại lệ đó sẽ là gì và chúng sẽ đại diện cho điều kiện gì. Trong kiểu try/finallysử dụng thông thường , trykhối đặt mọi thứ vào trạng thái mà mã bên ngoài sẽ không mong đợi và finallykhối khôi phục trạng thái của những thứ đó về trạng thái mà mã bên ngoài mong đợi. Mã bên ngoài bắt được ngoại lệ thường sẽ mong đợi rằng, bất chấp ngoại lệ, mọi thứ đã được khôi phục vềnormaltiểu bang. Ví dụ: giả sử một số mã bắt đầu một giao dịch và sau đó cố gắng thêm hai bản ghi; khối "cuối cùng" thực hiện hoạt động "quay lại nếu không được cam kết". Người gọi có thể được chuẩn bị cho một ngoại lệ xảy ra trong quá trình thực hiện thao tác "thêm" thứ hai và có thể mong đợi rằng nếu nó bắt được một ngoại lệ như vậy, cơ sở dữ liệu sẽ ở trạng thái như trước khi một trong hai thao tác được thử. Tuy nhiên, nếu một ngoại lệ thứ hai xảy ra trong quá trình khôi phục, thì những điều tồi tệ có thể xảy ra nếu người gọi đưa ra bất kỳ giả định nào về trạng thái cơ sở dữ liệu. Lỗi khôi phục dữ liệu đại diện cho một cuộc khủng hoảng lớn - một cuộc khủng hoảng không nên bị bắt bởi mã chỉ mong đợi một ngoại lệ "Không thêm được bản ghi".

Khuynh hướng cá nhân của tôi là có một phương pháp cuối cùng bắt các ngoại lệ xảy ra và bọc chúng trong một "CleanupFailedException", nhận ra rằng lỗi đó đại diện cho một vấn đề lớn và một ngoại lệ như vậy không nên được coi nhẹ.


2

Một giải pháp, nếu hai Ngoại lệ là hai lớp khác nhau

try {
    ...
    }
catch(package1.Exception err)
   {
    ...
   }
catch(package2.Exception err)
   {
   ...
   }
finally
  {
  }

Nhưng đôi khi bạn không thể tránh được lần thử thứ hai này. ví dụ để đóng một luồng

InputStream in=null;
try
 {
 in= new FileInputStream("File.txt");
 (..)// do something that might throw an exception during the analysis of the file, e.g. a SQL error
 }
catch(SQLException err)
 {
 //handle exception
 }
finally
 {
 //at the end, we close the file
 if(in!=null) try { in.close();} catch(IOException err) { /* ignore */ }
 }

Trong trường hợp của bạn, nếu bạn đã sử dụng câu lệnh "using", nó sẽ dọn dẹp tài nguyên.
Chuck Conway

Tệ của tôi, tôi cho rằng đó là C #.
Chuck Conway

1

Tại sao bạn muốn tránh khối bổ sung? Vì khối cuối cùng chứa các hoạt động "bình thường" có thể tạo ra một ngoại lệ VÀ bạn muốn khối cuối cùng chạy hoàn toàn, bạn PHẢI bắt các ngoại lệ.

Nếu bạn không mong đợi khối cuối cùng sẽ đưa ra một ngoại lệ và bạn không biết cách xử lý ngoại lệ (bạn sẽ chỉ kết xuất dấu vết ngăn xếp), hãy để ngoại lệ bong bóng ngăn xếp lệnh gọi (xóa try-catch khỏi cuối cùng khối).

Nếu bạn muốn giảm việc nhập, bạn có thể triển khai khối try-catch bên ngoài "toàn cầu", khối này sẽ bắt tất cả các ngoại lệ được đưa vào các khối cuối cùng:

try {
    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }

    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }

    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }
} catch (Exception ex) {
    ...
}

2
-1 Đối với cái này, quá. Điều gì sẽ xảy ra nếu bạn đang cố gắng đóng nhiều tài nguyên trong một khối cuối cùng? Nếu không đóng được tài nguyên đầu tiên, các tài nguyên khác sẽ vẫn mở khi ngoại lệ được ném ra.
Lập trình viên Outlaw

Đây là lý do tại sao tôi nói với Paul rằng bạn PHẢI nắm bắt các ngoại lệ nếu bạn muốn đảm bảo khối cuối cùng hoàn thành. Hãy đọc câu trả lời TOÀN BỘ!
Eduard Wirch

1

Sau nhiều lần cân nhắc, tôi thấy mã sau là tốt nhất:

MyResource resource = null;
try {
    resource = new MyResource();
    resource.doSomethingFancy();
    resource.close(); 
    resource = null;  
} finally {
    closeQuietly(resource)
}

void closeQuietly(MyResource a) {
    if (a!=null)
        try {
             a.close();
        } catch (Exception e) {
             //ignore
        }
}

Mã đó đảm bảo sau:

  1. Tài nguyên được giải phóng khi mã hoàn tất
  2. Các ngoại lệ được ném ra khi đóng tài nguyên sẽ không bị tiêu thụ nếu không xử lý chúng.
  3. Mã không cố gắng đóng tài nguyên hai lần, sẽ không có ngoại lệ không cần thiết nào được tạo.

Bạn cũng có thể tránh gọi resource.close (); resource = null trong khối try, đó là khối cuối cùng dành cho. Cũng lưu ý rằng bạn không xử lý bất kỳ ngoại lệ nào được ném ra trong khi "làm điều gì đó lạ mắt", điều này thực sự mà tôi nghĩ tốt hơn là xử lý các ngoại lệ cơ sở hạ tầng ở cấp ứng dụng cao hơn trong ngăn xếp.
Paul

Resource.close () cũng có thể ném và ngoại lệ - tức là khi quá trình xả bộ đệm không thành công. Ngoại lệ này không bao giờ được tiêu thụ. Tuy nhiên, nếu đóng luồng do ngoại lệ đã nêu trước đó, tài nguyên sẽ được đóng một cách lặng lẽ bỏ qua ngoại lệ và giữ nguyên nguyên nhân gốc.
Grogi

0

Nếu có thể, bạn nên kiểm tra để tránh tình trạng lỗi bắt đầu.

try{...}
catch(NullArgumentException nae){...}
finally
{
  //or if resource had some useful function that tells you its open use that
  if (resource != null) 
  {
      resource.Close();
      resource = null;//just to be explicit about it was closed
  }
}

Ngoài ra, bạn có thể chỉ nên bắt các ngoại lệ mà bạn có thể khôi phục, nếu bạn không thể khôi phục thì hãy để nó phổ biến đến cấp cao nhất của chương trình của bạn. Nếu bạn không thể kiểm tra điều kiện lỗi, bạn sẽ phải bao quanh mã của mình bằng một khối thử bắt giống như bạn đã làm (mặc dù tôi khuyên bạn vẫn nên bắt các lỗi cụ thể, được mong đợi).


Kiểm tra các điều kiện lỗi nói chung là một thực tiễn tốt, đơn giản vì các ngoại lệ rất tốn kém.
Dirk Vollmar

"Lập trình phòng thủ" là một mô hình đã lỗi thời. Mã cồng kềnh là kết quả của quá trình kiểm tra tất cả các điều kiện lỗi cuối cùng gây ra nhiều vấn đề hơn là nó giải quyết được. TDD và các ngoại lệ xử lý là cách tiếp cận hiện đại IMHO
Joe Soul-Bringer

@Joe - Tôi không đồng ý với bạn về việc kiểm tra tất cả các điều kiện lỗi, nhưng đôi khi nó có ý nghĩa, đặc biệt là do sự khác biệt (thông thường) trong chi phí của một lần kiểm tra đơn giản để tránh ngoại lệ so với chính ngoại lệ.
Ken Henderson

1
-1 Ở đây, resource.Close () có thể ném một ngoại lệ. Nếu bạn cần đóng các tài nguyên bổ sung, ngoại lệ sẽ khiến hàm quay trở lại và chúng sẽ vẫn mở. Đó là mục đích của lần thử / bắt thứ hai trong OP.
Lập trình viên ngoài vòng pháp luật

@Outlaw - bạn đang thiếu ý kiến ​​của tôi nếu Đóng ném một ngoại lệ và tài nguyên được mở sau đó bằng cách nắm bắt và ngăn chặn ngoại lệ, làm cách nào để khắc phục sự cố? Do đó, tại sao tôi lại để nó lan truyền (khá hiếm khi tôi có thể khôi phục khi nó vẫn mở).
Ken Henderson

0

Bạn có thể cấu trúc lại cấu trúc này thành một phương thức khác ...

public void RealDoSuff()
{
   try
   { DoStuff(); }
   catch
   { // resource.close failed or something really weird is going on 
     // like an OutOfMemoryException 
   }
}

private void DoStuff() 
{
  try 
  {}
  catch
  {
  }
  finally 
  {
    if (resource != null) 
    {
      resource.close(); 
    }
  }
}

0

Tôi thường làm điều này:

MyResource r = null;
try { 
   // use resource
} finally {   
    if( r != null ) try { 
        r.close(); 
    } catch( ThatSpecificExceptionOnClose teoc ){}
}

Cơ sở lý luận: Nếu tôi đã làm xong tài nguyên và vấn đề duy nhất tôi gặp phải là đóng nó, thì tôi không thể làm được gì nhiều về nó. Sẽ không có ý nghĩa gì khi giết toàn bộ chuỗi nếu tôi đã hoàn tất với tài nguyên.

Đây là một trong những trường hợp ít nhất đối với tôi, có thể an toàn khi bỏ qua ngoại lệ đã kiểm tra đó.

Cho đến ngày nay, tôi không gặp vấn đề gì khi sử dụng thành ngữ này.


Tôi sẽ ghi lại nó, đề phòng trường hợp bạn tìm thấy một số rò rỉ trong tương lai. Bằng cách đó, bạn sẽ biết họ có thể (không) đến từ đâu
Egwor

@Egwor. Tôi đồng ý với bạn. Đây chỉ là một số đoạn mã ngắn. Tôi đăng nhập nó quá và probaly sử dụng một nắm bắt một cái gì đó có thể được thực hiện với sự ngoại lệ :)
OscarRyz

0
try {
    final Resource resource = acquire();
    try {
        use(resource);
    } finally {
        resource.release();
    }
} catch (ResourceException exx) {
    ... sensible code ...
}

Công việc hoàn thành. Không có bài kiểm tra nào. Bắt đơn, bao gồm các ngoại lệ thu được và phát hành. Tất nhiên bạn có thể sử dụng thành ngữ Execute Around và chỉ phải viết nó một lần cho mỗi loại tài nguyên.


5
Điều gì sẽ xảy ra nếu use (resource) ném ngoại lệ A và sau đó resource.release () ném ngoại lệ B? Ngoại lệ A bị mất ...
Darron

0

Thay đổi Resourcetừ câu trả lời hay nhất thànhCloseable

Thực hiện các luồng CloseableDo đó bạn có thể sử dụng lại phương thức cho tất cả các luồng

protected void closeQuietly(Closeable resource) {
    if (resource == null) 
        return;
    try {
        resource.close();
    } catch (IOException e) {
        //log the exception
    }
}

0

Tôi đã gặp phải một tình huống tương tự trong đó tôi không thể sử dụng thử với tài nguyên nhưng tôi cũng muốn xử lý ngoại lệ đến từ khi đóng, không chỉ ghi nhật ký và bỏ qua nó như cơ chế closeQuietly. trong trường hợp của tôi, tôi không thực sự xử lý một luồng đầu ra, vì vậy lỗi khi đóng được quan tâm nhiều hơn là một luồng đơn giản.

IOException ioException = null;
try {
  outputStream.write("Something");
  outputStream.flush();
} catch (IOException e) {
  throw new ExportException("Unable to write to response stream", e);
}
finally {
  try {
    outputStream.close();
  } catch (IOException e) {
    ioException = e;
  }
}
if (ioException != null) {
  throw new ExportException("Unable to close outputstream", ioException);
}
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.