โยนข้อยกเว้นในบล็อกสุดท้าย


100

มีวิธีที่สวยงามในการจัดการข้อยกเว้นที่เกิดขึ้นในfinallyบล็อกหรือไม่?

ตัวอย่างเช่น:

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

คุณจะหลีกเลี่ยงtry/ catchในfinallyบล็อกได้อย่างไร?

คำตอบ:


72

ฉันมักจะทำเช่นนี้:

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

ที่อื่น:

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

4
ใช่ฉันใช้สำนวนที่คล้ายกันมาก แต่ฉันไม่สร้างฟังก์ชันสำหรับสิ่งนั้น
OscarRyz

9
ฟังก์ชันนี้มีประโยชน์หากคุณจำเป็นต้องใช้สำนวนไม่กี่แห่งในคลาสเดียวกัน
Darron

การตรวจสอบค่าว่างซ้ำซ้อน หากทรัพยากรเป็นโมฆะวิธีการเรียกใช้งานไม่ได้ควรได้รับการแก้ไข นอกจากนี้หากรีซอร์สเป็นโมฆะสิ่งนั้นควรถูกบันทึก มิฉะนั้นจะส่งผลให้มีการเพิกเฉยต่อข้อยกเว้นที่อาจเกิดขึ้น
Dave Jarvis

14
การตรวจสอบค่าว่างไม่ซ้ำซ้อนเสมอไป ให้นึกถึง "resource = new FileInputStream (" file.txt ")" เป็นบรรทัดแรกของการลอง นอกจากนี้คำถามนี้ไม่ได้เกี่ยวกับการเขียนโปรแกรมเชิงแง่มุมซึ่งหลาย ๆ คนไม่ได้ใช้ อย่างไรก็ตามแนวคิดที่ว่าไม่ควรละเลยข้อยกเว้นได้รับการจัดการอย่างกะทัดรัดที่สุดโดยการแสดงคำสั่งบันทึก
Darron

1
Resource=> Closeable?
Dmitry Ginzburg

25

ฉันมักจะใช้closeQuietlyวิธีใดวิธีหนึ่งในorg.apache.commons.io.IOUtils:

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

3
คุณสามารถทำให้วิธีนี้กว้างขึ้นได้ด้วย Closeable public static void closeQuietly (Closeable closeable) {
Peter Lawrey

6
ใช่ Closeable นั้นดี เป็นเรื่องน่าเสียดายที่หลาย ๆ สิ่ง (เช่นทรัพยากร JDBC) ไม่ได้นำไปใช้
Darron

22

หากคุณใช้ Java 7 และresourceใช้งานAutoClosableคุณสามารถทำได้ (โดยใช้ InputStream เป็นตัวอย่าง):

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

8

เหนือกว่าเล็กน้อย แต่อาจมีประโยชน์หากคุณปล่อยให้ข้อยกเว้นเกิดขึ้นและคุณไม่สามารถบันทึกอะไรจากภายในวิธีการของคุณได้ (เช่นเนื่องจากเป็นไลบรารีและคุณต้องการให้รหัสการโทรจัดการข้อยกเว้นและการบันทึก):

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

อัปเดต: ฉันตรวจสอบเรื่องนี้อีกเล็กน้อยและพบบล็อกโพสต์ที่ยอดเยี่ยมจากคนที่คิดอย่างชัดเจนเกี่ยวกับเรื่องนี้มากกว่าฉัน: http://illegalargumentexception.blogspot.com/2008/10/java-how-not-to-make -mess-of-stream.html เขาก้าวไปอีกขั้นและรวมสองข้อยกเว้นเข้าด้วยกันซึ่งฉันเห็นว่ามีประโยชน์ในบางกรณี


1
+1 สำหรับลิงก์บล็อก นอกจากนี้อย่างน้อยฉันก็จะบันทึกignoreข้อยกเว้น
Denis Kniazhev

6

ใน Java 7 คุณไม่จำเป็นต้องปิดรีซอร์สอย่างชัดเจนอีกต่อไปในที่สุดบล็อกแทนคุณสามารถใช้ไวยากรณ์try -with-resources คำสั่ง try-with-resources คือคำสั่ง try ที่ประกาศทรัพยากรอย่างน้อยหนึ่งรายการ ทรัพยากรเป็นวัตถุที่ต้องปิดหลังจากโปรแกรมเสร็จสิ้น คำสั่ง try-with-resources ช่วยให้มั่นใจได้ว่าทรัพยากรแต่ละรายการถูกปิดท้ายคำสั่ง อ็อบเจ็กต์ใด ๆ ที่ใช้ java.lang.AutoCloseable ซึ่งรวมถึงอ็อบเจ็กต์ทั้งหมดที่ใช้ java.io.Closeable สามารถใช้เป็นรีซอร์สได้

สมมติรหัสต่อไปนี้:

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

หากมีข้อยกเว้นเกิดขึ้นเมธอดปิดจะถูกเรียกใช้ทรัพยากรทั้งสามนี้ตามลำดับที่ตรงกันข้ามกับที่สร้างขึ้น หมายความว่าเมธอดปิดจะถูกเรียกก่อนสำหรับ ResultSetm จากนั้นคำสั่งและในตอนท้ายสำหรับอ็อบเจ็กต์การเชื่อมต่อ

สิ่งสำคัญคือต้องทราบว่าข้อยกเว้นใด ๆ ที่เกิดขึ้นเมื่อมีการเรียกวิธีการปิดโดยอัตโนมัติจะถูกระงับ ข้อยกเว้นที่ถูกระงับเหล่านี้สามารถเรียกคืนได้โดยเมธอด getsuppressed () ที่กำหนดไว้ในคลาสThrowable

ที่มา: https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html


ดูเหมือนจะไม่สมบูรณ์ที่คำตอบนี้ไม่ได้กล่าวถึงความแตกต่างของพฤติกรรมระหว่างแนวทางนี้และวิธีการทำงานของโค้ดตัวอย่างที่โพสต์ของ OP
Nathan Hughes

2
การใช้ try-with-resources จะทำให้เกิดข้อยกเว้นเมื่อปิดหากส่วนในบล็อกลองเสร็จสมบูรณ์ตามปกติ แต่วิธีปิดไม่แตกต่างจากรหัส OP การแนะนำให้ใช้แทนโดยไม่ยอมรับว่าพฤติกรรมเปลี่ยนไปอาจทำให้เข้าใจผิดได้
Nathan Hughes

ไม่แสดงข้อยกเว้นวิธีการปิดถูกเรียกโดยอัตโนมัติจะถูกระงับ
Soroosh

2
ลองใช้กรณีที่ฉันอธิบายไว้ ลองบล็อกเสร็จสิ้นตามปกติปิดพ่นบางสิ่ง และอ่านหน้าเว็บที่คุณโพสต์ลิงก์ไปอีกครั้งการปราบปรามจะมีผลเฉพาะเมื่อการบล็อกลองพ่นบางสิ่ง
Nathan Hughes

3

การเพิกเฉยต่อข้อยกเว้นที่เกิดขึ้นในบล็อก 'สุดท้าย' โดยทั่วไปเป็นความคิดที่ไม่ดีเว้นแต่จะมีใครรู้ว่าข้อยกเว้นเหล่านั้นจะเป็นอย่างไรและจะแสดงเงื่อนไขใด ในtry/finallyรูปแบบการใช้งานปกติtryบล็อกจะวางสิ่งต่างๆไว้ในสถานะที่โค้ดภายนอกจะไม่คาดหวังและfinallyบล็อกจะคืนสถานะของสิ่งเหล่านั้นให้เป็นสิ่งที่โค้ดภายนอกคาดหวัง รหัสภายนอกที่จับข้อยกเว้นโดยทั่วไปจะคาดหวังว่าแม้จะมีข้อยกเว้นทุกอย่างได้รับการกู้คืนเป็นไฟล์normalสถานะ. ตัวอย่างเช่นสมมติว่ารหัสบางรหัสเริ่มต้นธุรกรรมแล้วพยายามเพิ่มสองระเบียน บล็อก "ในที่สุด" จะดำเนินการ "ย้อนกลับหากไม่ได้กระทำ" ผู้โทรอาจเตรียมพร้อมสำหรับข้อยกเว้นที่จะเกิดขึ้นในระหว่างการดำเนินการของการดำเนินการ "เพิ่ม" ครั้งที่สองและอาจคาดหวังว่าหากพบข้อยกเว้นดังกล่าวฐานข้อมูลจะอยู่ในสถานะก่อนที่จะพยายามดำเนินการอย่างใดอย่างหนึ่ง อย่างไรก็ตามหากมีข้อยกเว้นที่สองเกิดขึ้นระหว่างการย้อนกลับสิ่งที่ไม่ดีอาจเกิดขึ้นได้หากผู้โทรตั้งสมมติฐานเกี่ยวกับสถานะฐานข้อมูล ความล้มเหลวในการย้อนกลับแสดงถึงวิกฤตครั้งใหญ่ซึ่งเป็นสิ่งที่โค้ดไม่ควรถูกจับโดยคาดว่าจะมีข้อยกเว้น "Failed to add record" เท่านั้น

ความชอบส่วนตัวของฉันคือการมีข้อยกเว้นในการจับวิธีสุดท้ายที่เกิดขึ้นและรวมไว้ใน "CleanupFailedException" โดยตระหนักว่าความล้มเหลวดังกล่าวเป็นปัญหาสำคัญและไม่ควรจับข้อยกเว้นดังกล่าว


2

วิธีแก้ปัญหาหนึ่งหากข้อยกเว้นทั้งสองเป็นสองคลาสที่แตกต่างกัน

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

แต่บางครั้งคุณไม่สามารถหลีกเลี่ยงการลองจับครั้งที่สองนี้ได้ เช่นการปิดสตรีม

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

ในกรณีของคุณถ้าคุณใช้คำสั่ง "ใช้" คำสั่งนี้ควรล้างทรัพยากร
Chuck Conway

ฉันไม่ดีฉันสมมติว่าเป็น C #
Chuck Conway

1

เหตุใดคุณจึงต้องการหลีกเลี่ยงการบล็อกเพิ่มเติม เนื่องจากในที่สุดบล็อกมีการดำเนินการ "ปกติ" ซึ่งอาจทำให้เกิดข้อยกเว้นและคุณต้องการให้การบล็อกในที่สุดทำงานได้อย่างสมบูรณ์คุณจึงต้องจับข้อยกเว้น

หากคุณไม่คาดหวังว่าบล็อกในที่สุดจะโยนข้อยกเว้นและคุณไม่รู้วิธีจัดการกับข้อยกเว้นอยู่ดี (คุณแค่ทิ้งสแต็กแทร็ก) ปล่อยให้ข้อยกเว้นเพิ่มขึ้นใน call stack (ลบ try-catch ออกจากที่สุด บล็อก).

หากคุณต้องการลดการพิมพ์คุณสามารถใช้บล็อกทดลองจับด้านนอก "ทั่วโลก" ซึ่งจะจับข้อยกเว้นทั้งหมดที่เกิดขึ้นในบล็อกสุดท้าย:

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

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

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

2
-1 สำหรับอันนี้ด้วย จะเกิดอะไรขึ้นหากคุณพยายามปิดทรัพยากรหลายรายการในบล็อกเดียวในที่สุด? หากการปิดทรัพยากรแรกล้มเหลวทรัพยากรอื่น ๆ จะยังคงเปิดอยู่เมื่อเกิดข้อยกเว้น
Outlaw Programmer

นี่คือเหตุผลที่ฉันบอกพอลว่าคุณต้องจับข้อยกเว้นถ้าคุณต้องการให้แน่ใจว่าบล็อกเสร็จสมบูรณ์ในที่สุด โปรดอ่านคำตอบทั้งหมด!
Eduard Wirch

1

หลังจากพิจารณาหลายครั้งฉันพบว่ารหัสต่อไปนี้ดีที่สุด:

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

รหัสนั้นรับประกันสิ่งต่อไปนี้:

  1. ทรัพยากรจะถูกปลดปล่อยเมื่อโค้ดเสร็จสิ้น
  2. ข้อยกเว้นที่เกิดขึ้นเมื่อปิดทรัพยากรจะไม่ถูกใช้โดยไม่ประมวลผล
  3. รหัสไม่พยายามปิดทรัพยากรสองครั้งจะไม่มีการสร้างข้อยกเว้นที่ไม่จำเป็น

คุณยังสามารถหลีกเลี่ยงการเรียก resource.close (); ทรัพยากร = null ในบล็อกลองนั่นคือสิ่งที่บล็อกมีไว้สำหรับในที่สุด นอกจากนี้โปรดทราบว่าคุณไม่ได้จัดการกับข้อยกเว้นใด ๆ ที่เกิดขึ้นในขณะที่ "ทำอะไรแปลก ๆ " ซึ่งจริงๆแล้วฉันคิดว่าฉันชอบที่จะจัดการข้อยกเว้นโครงสร้างพื้นฐานในระดับแอปพลิเคชันที่สูงกว่าลงมาเป็นกอง
Paul

resource.close () อาจโยนและยกเว้นเช่นกันเช่นเมื่อการล้างบัฟเฟอร์ล้มเหลว ไม่ควรบริโภคข้อยกเว้นนี้ อย่างไรก็ตามหากปิดสตรีมอันเป็นผลมาจากข้อยกเว้นที่เพิ่มขึ้นก่อนหน้านี้ทรัพยากรควรปิดอย่างเงียบ ๆ โดยไม่สนใจข้อยกเว้นและรักษาสาเหตุที่แท้จริง
Grogi

0

หากทำได้คุณควรทดสอบเพื่อหลีกเลี่ยงเงื่อนไขข้อผิดพลาดที่จะเริ่มต้นด้วย

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

นอกจากนี้คุณควรจับเฉพาะข้อยกเว้นที่คุณสามารถกู้คืนได้หากคุณไม่สามารถกู้คืนได้ให้ปล่อยให้เผยแพร่ไปยังระดับบนสุดของโปรแกรมของคุณ หากคุณไม่สามารถทดสอบเงื่อนไขข้อผิดพลาดที่คุณจะต้องล้อมรอบโค้ดของคุณด้วยบล็อกลองจับเหมือนที่คุณเคยทำมาแล้ว (แม้ว่าฉันจะแนะนำให้ยังคงตรวจจับข้อผิดพลาดเฉพาะที่คาดไว้)


การทดสอบเงื่อนไขข้อผิดพลาดโดยทั่วไปเป็นแนวทางปฏิบัติที่ดีเพียงเพราะข้อยกเว้นมีราคาแพง
Dirk Vollmar

"การเขียนโปรแกรมเชิงป้องกัน" เป็นกระบวนทัศน์ที่ล้าสมัย รหัสป่องซึ่งเป็นผลมาจากการทดสอบเงื่อนไขข้อผิดพลาดทั้งหมดทำให้เกิดปัญหามากกว่าที่จะแก้ได้ในที่สุด TDD และข้อยกเว้นในการจัดการคือแนวทางสมัยใหม่ IMHO
Joe Soul-bringer

@ โจ - ฉันไม่เห็นด้วยกับคุณในการทดสอบเงื่อนไขข้อผิดพลาดทั้งหมด แต่บางครั้งก็สมเหตุสมผลโดยเฉพาะอย่างยิ่งในแง่ของความแตกต่าง (ปกติ) ในค่าใช้จ่ายของการตรวจสอบง่ายๆเพื่อหลีกเลี่ยงข้อยกเว้นกับข้อยกเว้นนั้นเอง
Ken Henderson

1
-1 ที่นี่ resource.Close () สามารถโยนข้อยกเว้น หากคุณต้องการปิดทรัพยากรเพิ่มเติมข้อยกเว้นจะทำให้ฟังก์ชันกลับมาและจะยังคงเปิดอยู่ นั่นคือจุดประสงค์ของการลอง / จับครั้งที่สองใน OP
Outlaw Programmer

@Outlaw - คุณกำลังพลาดประเด็นของฉันถ้า Close แสดงข้อยกเว้นและทรัพยากรจะเปิดขึ้นโดยการจับและระงับข้อยกเว้นฉันจะแก้ไขปัญหาได้อย่างไร เหตุใดฉันจึงปล่อยให้มันแพร่กระจาย (มันค่อนข้างหายากที่ฉันสามารถกู้คืนได้โดยที่ยังเปิดอยู่)
Ken Henderson

0

คุณสามารถ refactor นี้เป็นวิธีอื่น ...

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

ฉันมักจะทำสิ่งนี้:

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

เหตุผล: ถ้าฉันใช้ทรัพยากรเสร็จแล้วและปัญหาเดียวที่ฉันมีคือการปิดมันไม่มีอะไรมากที่ฉันสามารถทำได้ มันไม่สมเหตุสมผลเลยที่จะฆ่าเธรดทั้งหมดถ้าฉันใช้ทรัพยากรเสร็จแล้ว

นี่เป็นหนึ่งในกรณีที่อย่างน้อยสำหรับฉันก็ปลอดภัยที่จะเพิกเฉยต่อการตรวจสอบข้อยกเว้นนั้น

จนถึงวันนี้ฉันไม่มีปัญหาในการใช้สำนวนนี้


ฉันจะบันทึกไว้เผื่อว่าคุณจะพบการรั่วไหลในอนาคต ด้วยวิธีนี้คุณจะรู้ว่าพวกเขา (ไม่) มาจาก
ไหน

@ เอิ๊กวอ. ฉันเห็นด้วยกับคุณ. นี่เป็นเพียงตัวอย่างสั้น ๆ ฉันบันทึกไว้ด้วยและการใช้การจับเป็นสิ่งที่สามารถทำได้โดยมีข้อยกเว้น :)
OscarRyz

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

งานเสร็จแล้ว ไม่มีการทดสอบค่าว่าง จับเดี่ยวรวมถึงข้อยกเว้นการได้มาและการปล่อย แน่นอนคุณสามารถใช้สำนวน Execute Around ได้และต้องเขียนเพียงครั้งเดียวสำหรับทรัพยากรแต่ละประเภท


5
จะเกิดอะไรขึ้นถ้า use (resource) พ่น Exception A จากนั้น resource.release () จะแสดงข้อยกเว้น B? ข้อยกเว้น A หายไป ...
Darron

0

เปลี่ยนResourceจากคำตอบที่ดีที่สุดเป็นCloseable

สตรีมดำเนินการCloseableดังนั้นคุณสามารถใช้วิธีนี้ซ้ำสำหรับสตรีมทั้งหมด

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

0

ฉันพบสถานการณ์ที่คล้ายกันที่ฉันไม่สามารถใช้ try กับทรัพยากรได้ แต่ฉันก็ต้องการจัดการข้อยกเว้นที่มาจากการปิดไม่ใช่แค่บันทึกและเพิกเฉยเหมือนกลไก closeQuietly ในกรณีของฉันฉันไม่ได้จัดการกับสตรีมเอาท์พุตจริง ๆ ดังนั้นความล้มเหลวในการปิดจึงน่าสนใจมากกว่าสตรีมธรรมดา

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);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.