2010-11-04 15 views
35

Trong khi làm việc trên một dự án trường học, tôi đã viết đoạn mã sau:Java try/catch/finally thực hành tốt nhất trong khi mua/nguồn lực đóng

FileOutputStream fos; 
ObjectOutputStream oos; 
try { 
    fos = new FileOutputStream(file); 
    oos = new ObjectOutputStream(fos); 

    oos.writeObject(shapes); 
} catch (FileNotFoundException ex) { 
    // complain to user 
} catch (IOException ex) { 
    // notify user 
} finally { 
    if (oos != null) oos.close(); 
    if (fos != null) fos.close(); 
} 

Vấn đề là Netbeans là nói cho tôi những resource.close() dòng ném một IOException và do đó phải bị bắt hoặc tuyên bố. Nó cũng phàn nàn rằng oosfos có thể chưa được khởi tạo (mặc dù các kiểm tra null).

Điều này có vẻ hơi lạ, xem như thế nào là toàn bộ điểm là dừng ngay IOException ngay tại đó.

My sửa chữa đầu gối giật là để làm điều này:

} finally { 
    try { 
     if (oos != null) oos.close(); 
     if (fos != null) fos.close(); 
    } catch (IOException ex) { } 
} 

Nhưng sâu thẳm này làm tôi bực mình và cảm thấy bẩn.

Tôi đến từ nền C#, nơi tôi chỉ đơn giản là tận dụng lợi thế của khối using, vì vậy tôi không chắc chắn về cách "đúng" là xử lý việc này.

Điều gì đúng cách để xử lý vấn đề này?

+5

Lưu ý rằng sửa chữa đầu gối giật bạn sẽ không đóng 'fos' khi 'oos.close() 'ném' IOException'. Mỗi cần phải đi thử riêng của mình. 'if (oos! = null) thử {oos.close()} catch (IOException logOrIgnore) {}' và cứ thế. – BalusC

+2

@ BalusC - đó là một điểm tốt. Cảm ơn ... bắt ... rằng: D –

+1

Thực ra, oos.close() cũng đóng các fos cơ bản. – SimonJ

Trả lời

46

Nếu bạn đang cố gắng để bắt và báo cáo tất cả các trường hợp ngoại lệ tại nguồn, một giải pháp tốt hơn là thế này:

ObjectOutputStream oos = null; 
try { 
    oos = new ObjectOutputStream(new FileOutputStream(file)); 
    oos.writeObject(shapes); 
    oos.flush(); 
} catch (FileNotFoundException ex) { 
    // complain to user 
} catch (IOException ex) { 
    // notify user 
} finally { 
    if (oos != null) { 
     try { 
      oos.close(); 
     } catch (IOException ex) { 
      // ignore ... any significant errors should already have been 
      // reported via an IOException from the final flush. 
     } 
    } 
} 

Ghi chú:

  • Dòng trình bao bọc Java chuẩn, trình đọc và nhà văn đều tuyên truyền close a nd flush vào các luồng được bao bọc của chúng, v.v. Vì vậy, bạn chỉ cần đóng hoặc tuôn ra trình bao bọc ngoài cùng.
  • Mục đích của xả nước một cách rõ ràng ở cuối khối thử là để bộ xử lý (thực) cho IOException được xem bất kỳ lỗi ghi nào .
  • Khi bạn thực hiện đóng hoặc tuôn ra trên luồng đầu ra, có một cơ hội "một lần trong một mặt trăng xanh" là một ngoại lệ sẽ bị ném do lỗi đĩa hoặc toàn bộ hệ thống tệp. Bạn không nên bí mật ngoại lệ này!.

Nếu bạn thường xuyên phải "đóng một dòng suối có thể vô phớt lờ IOExceptions", sau đó bạn có thể viết cho mình một phương pháp helper như thế này:

public void closeQuietly(Closeable closeable) { 
    if (closeable != null) { 
     try { 
      closeable.close(); 
     } catch (IOException ex) { 
      // ignore 
     } 
    } 
} 

sau đó bạn có thể thay thế trước khối finally với:

} finally { 
    closeQuietly(oos); 
} 

(câu trả lời khác chỉ ra rằng một phương pháp closeQuietly là đã có sẵn trong một thư viện Apache Commons ... nếu bạn không nhớ thêm một phụ thuộc vào dự án của bạn cho một phương pháp 10 dòng.).

Nhưng hãy cẩn thận rằng bạn chỉ sử dụng closeQuietly trên các luồng có ngoại lệ IO thực sự không liên quan.

1 - Điều đó không cần thiết khi sử dụng tài nguyên thử.


Về vấn đề flush() so close() mà mọi người đang hỏi về:

  • Tiêu chuẩn "bộ lọc" và "đệm" suối đầu ra và tác giả có một hợp đồng API mà nói rằng close() gây ra tất cả đầu ra đệm được xả. Bạn nên thấy rằng tất cả các lớp đầu ra (tiêu chuẩn) khác làm đệm đầu ra sẽ hoạt động theo cùng một cách. Vì vậy, đối với một lớp tiêu chuẩn, nó là không cần thiết để gọi flush() ngay trước close().
  • Đối với các lớp tùy chỉnh và bên thứ ba, bạn cần điều tra (ví dụ: đọc javadoc, xem mã), nhưng bất kỳ phương pháp nào close() không xóa dữ liệu đệm được cho là bị hỏng.
  • Cuối cùng, có sự cố về những gì flush() thực sự thực hiện. Những gì javadoc nói là thế này (đối với OutputStream ...)

    Nếu nơi dự định của dòng này là một sự trừu tượng được cung cấp bởi hệ điều hành cơ bản, ví dụ như một tập tin, sau đó xả nước đảm bảo dòng duy nhất mà byte trước được ghi vào luồng được chuyển đến hệ điều hành để viết; nó không đảm bảo rằng chúng thực sự được ghi vào một thiết bị vật lý như ổ đĩa.

    Vì vậy, nếu bạn hy vọng/hãy tưởng tượng rằng gọi số flush() đảm bảo rằng dữ liệu của bạn sẽ vẫn tồn tại, bạn đã sai! (Nếu bạn cần làm điều đó, hãy xem phương thức FileChannel.force ...)


Mặt khác, nếu bạn có thể sử dụng Java 7 hoặc mới hơn, "mới" thử-với-các nguồn lực như mô tả trong câu trả lời @ Mike Clark là giải pháp tốt nhất.

Nếu bạn không sử dụng Java 7 trở lên cho mã mới của mình, có thể bạn đang ở trong một lỗ và đào sâu hơn.

+1

+1 để chỉ ra vấn đề tuôn ra, đó là vấn đề thường bị bỏ lỡ nhất. – Yishai

+1

+1 Nó thực sự hữu ích !!!!!! – Novemberland

+1

Điều này có vẻ rất bẩn từ đầu đến cuối. Java thực sự là loại kỳ lạ ?? – Blauhirn

1

Rất tiếc, không có hỗ trợ mức độ ngôn ngữ. Nhưng có rất nhiều thư viện ở đó khiến việc này trở nên đơn giản. Kiểm tra thư viện commons-io. Hoặc hiện đại google-guava @http://guava-libraries.googlecode.com/svn/trunk/javadoc/index.html

+3

Cụ thể hơn, Closeables.closeQuietly được ghi lại ở đây: http://guava-libraries.googlecode.com/svn/trunk/javadoc/com/ google/common/io/Closeables.html # closeQuietly (java.io.Closeable) – SimonJ

+0

+1 @SimonJ để có liên kết hữu ích. Tôi đọc stackoverflow nhiều hơn, càng có nhiều ổi là một điều thực sự hữu ích (tm). – orangepips

1

Bạn đang làm đúng. Nó cũng làm phiền tôi. Bạn nên khởi tạo các luồng đó thành null một cách rõ ràng - đó là quy ước chung. Tất cả những gì bạn có thể làm là tham gia câu lạc bộ và muốn có using.

+0

Bạn không thể bỏ qua các kiểm tra null; 'fos' có thể mở ra, điều này sẽ gây ra sự cố gắng đóng' oos' để ném NullPointerException nếu không được kiểm tra null. –

+0

Cảm ơn bạn đã "bắt". Chơi tốt, huh? :) – Mike

+0

Không bao giờ bắt NullPointerException hoặc ngoại lệ! –

1

Không phải là câu trả lời trực tiếp cho vấn đề của bạn nhưng thực tế không may là bởi vì finallycatch đều được liên kết với try mọi người nghĩ rằng chúng thuộc về nhau. Thiết kế tốt nhất cho các khối try là có một số catch hoặc finally nhưng không phải cả hai.

Trong trường hợp này, nhận xét của bạn gợi ý rằng có điều gì đó sai. Tại sao, trong một phương thức xử lý tệp IO, chúng ta có phàn nàn về bất cứ điều gì với người dùng không. Chúng tôi có thể đang chạy sâu trên một máy chủ ở đâu đó với một người dùng trong tầm nhìn.

Vì vậy, mã bạn trình bày ở trên phải có finally để không thành công khi gặp sự cố. Nó thiếu khả năng xử lý thông minh với các lỗi tuy nhiên, do đó, catch của bạn thuộc về một nơi nào đó cao hơn trên chuỗi cuộc gọi.

+1

Không đồng ý với phần "hoặc không phải cả hai"; Tôi có rất nhiều ví dụ mà tôi cần để thực hiện một số xử lý ngoại lệ, nhưng hoàn toàn phải có một cuối cùng để làm sạch tài nguyên. –

+0

Thông thường, tôi đồng ý với bạn 100%, và tôi là tất cả về việc duy trì trách nhiệm đúng đắn và những gì không, nhưng đây là một dự án trường học nhanh với một giao diện đơn giản (một lớp). về tệp IO trong Java. Bạn nên vui vì tôi đã không đặt tất cả các mã trong xử lý sự kiện trong một ActionListener ẩn danh! : D –

+0

@Software Monkey - Cuộc gọi công bằng, tôi không ngụ ý ngụ ý rằng bạn không nên cuối cùng chặn, hoàn toàn ngược lại trong thực tế. Nó chỉ là bằng cách sử dụng hoặc là một khối cuối cùng hoặc bắt khối (s) bạn nhận được cùng một ngữ nghĩa như C# 's sử dụng khối, mặc dù nếu các trường hợp ngoại lệ được kiểm tra, bạn sẽ phải explicity đánh dấu phương pháp như ném chúng. – CurtainDog

4

Tôi thường có lớp học nhỏ IOUtil với phương pháp như:

public static void close(Closeable c) { 
    if (c != null) { 
     try { 
      c.close(); 
     } 
     catch (IOException e) { 
      // ignore or log 
     } 
    } 
} 
+2

Đây chắc chắn là cách tiếp cận ưa thích của tôi. Tôi luôn đảm bảo đăng nhập Ngoại lệ khi đóng. Nó có thể là một dấu hiệu của một cái gì đó khó chịu xảy ra. Mặc dù nó không nên dừng hoạt động bình thường, tôi thực sự cảm thấy một lỗi ở đây nên được đăng nhập một nơi nào đó. – Dunderklumpen

3

Còn anh chàng này thì sao? Không có kiểm tra null, không có gì ngạc nhiên. Tất cả mọi thứ được làm sạch khi thoát.

try { 
    final FileOutputStream fos = new FileOutputStream(file); 
    try { 
     final ObjectOutputStream oos = new ObjectOutputStream(fos); 
     try { 
      oos.writeObject(shapes); 
      oos.flush(); 
     } 
     catch(IOException ioe) { 
      // notify user of important exception 
     } 
     finally { 
      oos.close(); 
     } 
    } 
    finally { 
     fos.close(); 
    } 
} 
catch (FileNotFoundException ex) { 
    // complain to user 
} 
catch (IOException ex) { 
    // notify user 
} 
+0

Không - một ngoại lệ được ném bởi gần sẽ thay thế ngoại lệ thông tin được ném bởi các hoạt động tệp thực tế. –

+0

bạn có nghĩa là ngoại lệ từ fos.close() ngoại lệ eclipsing từ oos.writeObject()? Hãy nắm bắt nó và xử lý nó sau đó. (Bản gốc đã được chỉnh sửa) – RAY

+1

Mã RAY là gần nhất với những gì Java 7 ARM sẽ viết cho bạn. Ngoại trừ, Java 7 ARM sẽ nhớ bất kỳ ngoại lệ nào gặp phải trong thử {} và cuối cùng sẽ ném tùy chọn này vào bất kỳ trường hợp ngoại lệ nào có thể xảy ra cuối cùng {}. –

12

Java 7 sẽ thêm Automatic Resource Management khối. Chúng rất giống với C# 's using.

Josh Bloch đã viết the technical proposal, tôi khuyên bạn nên đọc. Không chỉ vì nó sẽ cung cấp cho bạn một chân lên trên một tính năng ngôn ngữ Java 7 sắp tới, nhưng vì đặc tả này thúc đẩy sự cần thiết cho một cấu trúc như vậy, và làm như vậy, minh họa cách viết mã đúng ngay cả khi không có ARM.

Dưới đây là một ví dụ về mã của Người hỏi, dịch sang hình thức ARM:

try (FileOutputStream fos = new FileOutputStream(file); 
     ObjectOutputStream oos = new ObjectOutputStream(fos)) 
{ 
    oos.writeObject(shapes); 
} 
catch (FileNotFoundException ex) 
{ 
    // handle the file not being found 
} 
catch (IOException ex) 
{ 
    // handle some I/O problem 
} 
+0

ARM cũng sẽ gọi 'flush();' trước 'close();'? – Muddz

+1

@Muddz Không, ARM sẽ không gọi xả.Cần lưu ý rằng hầu hết các lớp Java phổ biến được sử dụng theo cách thông thường không yêu cầu bạn gọi flush() trước khi gọi close(), vì việc thực hiện mức thấp hơn của close() thường làm đủ để tạo ra một kết thúc rõ ràng dòng chảy tuôn ra không cần thiết. Thật không may hợp đồng tuôn ra/đóng này không được ghi lại, nó chỉ là một hành vi tiềm ẩn đã biết mà mọi người thường tin tưởng. Trong những trường hợp cụ thể mà bạn biết bạn cần gọi flush() trước close(), bạn có thể trang trí luồng của mình, ví dụ: 'MyFlushOnCloseOutputStream mới (originalOutputStream)'. –

+0

Tốt hơn là nó giúp tôi không lo lắng về điều đó. Cảm ơn lời giải thích! – Muddz

21

thực hành tốt nhất hiện nay đối với try/catch/finally liên quan đến đối tượng là closeable (ví dụ như tập tin) là sử dụng Java 7 của try-với tuyên bố -resource, ví dụ:

try (FileReader reader = new FileReader("ex.txt")) { 
    System.out.println((char)reader.read()); 
} catch (IOException ioe) { 
    ioe.printStackTrace(); 
} 

Trong trường hợp này, FileReader sẽ tự động đóng ở cuối câu lệnh thử, mà không cần đóng nó trong một khối cuối cùng rõ ràng. Có một vài ví dụ ở đây:

http://ppkwok.blogspot.com/2012/11/java-cafe-2-try-with-resources.html

Mô tả Java chính thức là tại địa chỉ:

http://docs.oracle.com/javase/7/docs/technotes/guides/language/try-with-resources.html

Các vấn đề liên quan