ไม่พบข้อยกเว้น Java?


170

ฉันมีปัญหาทางทฤษฎีเล็กน้อยกับการสร้างแบบลองจับ

ฉันสอบภาคปฏิบัติเมื่อวานนี้เกี่ยวกับ Java และฉันไม่เข้าใจตัวอย่างต่อไปนี้:

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());
}

คำถามคือ "ผลลัพธ์จะเป็นอย่างไร"

ฉันค่อนข้างแน่ใจว่ามันจะเป็น AB2C3 แต่น่าแปลกใจ แต่ก็ไม่เป็นความจริง

คำตอบที่ถูกต้องคือ ABC3 (ทดสอบแล้วและเป็นแบบนั้นจริงๆ)

คำถามของฉันคือข้อยกเว้น ("2") หายไปไหน


8
+1 ชายอ่าฉันรู้คำตอบนี้ ฉันถูกถามเรื่องนี้ในการสัมภาษณ์ มันเป็นคำถามที่ดีมากสำหรับการทำความเข้าใจวิธีลอง / จับ / ในที่สุดก็ทำงานบนกองซ้อน
แต่ฉันไม่ใช่คลาส Wrapper

10
มีเพียงคำสั่งพิมพ์หนึ่งที่สามารถพิมพ์ตัวเลข (ที่ผ่านมา: print(e.getMessage())) คุณคิดว่าการส่งออกจะเป็นAB2C3: คุณคิดว่าcatchบล็อกด้านนอกจะถูกดำเนินการสองครั้ง?
Adrian Pronk

ใน java ก่อนที่คำสั่งที่ถ่ายโอนการควบคุมออกจาก catch block จะถูกดำเนินการบล็อกสุดท้ายจะถูกดำเนินการหากมีอยู่ หากเพียงรหัสในที่สุดบล็อกไม่ได้ถ่ายโอนการควบคุมไปยังภายนอกคำแนะนำล่าช้าจากการจับบล็อกได้รับการดำเนินการ
โธมัส

คำตอบ:


198

จากข้อกำหนดภาษา Java 14.20.2 :

หากบล็อก catch เสร็จสมบูรณ์อย่างกะทันหันด้วยเหตุผล R ดังนั้นบล็อกสุดท้ายจะถูกดำเนินการ จากนั้นมีทางเลือกคือ:

  • หากการบล็อกสุดท้ายเสร็จสมบูรณ์ตามปกติคำสั่งลองจะเสร็จสมบูรณ์โดยทันทีด้วยเหตุผล R

  • หากบล็อกสุดท้ายเสร็จสมบูรณ์ทันทีด้วยเหตุผล S แล้วลองคำสั่งเสร็จสิ้นลงอย่างกะทันหันด้วยเหตุผล S (และเหตุผล R จะถูกยกเลิก)

ดังนั้นเมื่อมี catch catch ที่ส่งข้อยกเว้น:

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

แต่ในที่สุดก็มีบล็อกที่ทำให้เกิดข้อยกเว้น:

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

Exception("2")จะถูกยกเลิกและException("3")จะถูกเผยแพร่เท่านั้น


72
เรื่องนี้ถือเป็นจริงสำหรับreturnงบ หากบล็อกสุดท้ายของคุณมีผลตอบแทนบล็อกจะกลับมาแทนที่ในtryหรือcatchบล็อก เนื่องจากคุณสมบัติ "" เหล่านี้จึงมีแนวปฏิบัติที่ดีคือในที่สุดบล็อกไม่ควรทิ้งข้อยกเว้นหรือมีข้อความสั่งคืน
Augusto

นี่เป็นข้อดีที่ได้จากการลองใช้กับทรัพยากรใน Java 7 ซึ่งจะเก็บรักษาข้อยกเว้นเริ่มต้นหากมีการสร้างข้อยกเว้นสำรองเมื่อปิดทรัพยากรโดยทั่วไปจะทำให้การดีบักง่ายขึ้น
w25r

19

ข้อยกเว้นที่ถูกโยนเข้ามาในที่สุดบล็อกระงับข้อยกเว้นที่ถูกโยนก่อนหน้านี้ในลองหรือจับบล็อก

ตัวอย่าง Java 7: http://ideone.com/0YdeZo

จากตัวอย่างของ 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();
    }
}

อย่างไรก็ตามในตัวอย่างนี้หากเมธอด readLine และปิดทั้งข้อยกเว้นการโยนดังนั้นเมธอด readFirstLineFromFileWithFinallyBlock จะโยนข้อยกเว้นที่ส่งออกจากบล็อกในที่สุด ข้อยกเว้นที่ส่งออกจากบล็อกการลองถูกระงับ


try-withไวยากรณ์ใหม่ของ Java 7 เพิ่มขั้นตอนการยกเว้นอีกขั้น: ข้อยกเว้นที่เกิดขึ้นในการลองบล็อกจะระงับสิ่งที่ถูกโยนก่อนหน้านี้ในส่วนลองกับ

จากตัวอย่างเดียวกัน:

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());
        }
    }

ข้อยกเว้นสามารถถูกส่งออกจากบล็อกของรหัสที่เกี่ยวข้องกับคำสั่งลองกับทรัพยากร ในตัวอย่างด้านบนข้อยกเว้นสามารถถูกส่งออกมาจากบล็อกการลองและสามารถยกเว้นได้ถึงสองข้อยกเว้นจากคำสั่ง try-with-resources เมื่อพยายามปิดวัตถุ ZipFile และ BufferedWriter หากข้อยกเว้นถูกส่งออกมาจากบล็อกการลองและข้อยกเว้นอย่างน้อยหนึ่งข้อถูกส่งออกไปจากคำสั่ง try-with-resources ดังนั้นข้อยกเว้นที่ส่งออกมาจากคำสั่ง try-with-resources จะถูกระงับและข้อยกเว้นการส่งออกโดยบล็อกจะเป็นข้อยกเว้น ที่ถูกโยนโดยเมธอด writeToFileZipFileContents คุณสามารถดึงข้อยกเว้นที่ถูกระงับเหล่านี้ได้โดยการเรียกใช้เมธอด Throwable.getSuppressed จากข้อยกเว้นที่ส่งออกมาโดยบล็อกการลอง


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

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


9

เนื่องจากthrow new Exception("2");ถูกโยนจากcatchบล็อกและไม่ใช่tryจะไม่ถูกจับอีกครั้ง
ดู14.20.2 การดำเนินการของลองที่สุดและลองจับในที่สุด

นี่คือสิ่งที่เกิดขึ้น:

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
}

ใช่ถูกต้องฉันเห็นว่าสิ่งนี้กำลังเกิดขึ้น แต่ฉันกำลังมองหาคำอธิบาย - ทำไมมันถึงมีพฤติกรรมแบบนี้
Kousalik

5

คำถามของคุณชัดเจนมากและคำตอบนั้นง่ายในระดับเดียวกัน .. วัตถุการยกเว้นที่มีข้อความเป็น "2" จะถูกเขียนทับโดยวัตถุ Exception ที่มีข้อความเป็น "3"

คำอธิบาย: เมื่อมีข้อยกเว้นเกิดขึ้นวัตถุจะถูกโยนเพื่อจับบล็อกที่จะจัดการ แต่เมื่อมีข้อยกเว้นเกิดขึ้นใน catch block ตัวของมันเองวัตถุนั้นจะถูกถ่ายโอนไปยัง OUTER CATCH Block (ถ้ามี) เพื่อการยกเว้น และเกิดขึ้นที่นี่เหมือนกัน วัตถุยกเว้นที่มีข้อความ "2" ถูกถ่ายโอนไปยัง OUTER catch block แต่เดี๋ยวก่อน .. ก่อนออกจากบล็อก try-catch ด้านในจะต้องดำเนินการในที่สุด นี่คือการเปลี่ยนแปลงที่เรากังวล วัตถุยกเว้นใหม่ (พร้อมข้อความ "3") ถูกส่งออกไปหรือบล็อกนี้ในที่สุดซึ่งแทนที่วัตถุยกเว้นที่ส่งออกไปแล้ว (ด้วยข้อความ "2") ผลที่เกิดขึ้นเมื่อมีการพิมพ์ข้อความของวัตถุยกเว้นเราได้ ค่าที่ถูกแทนที่คือ "3" และไม่ใช่ "2"

โปรดจำไว้ว่า: มีข้อยกเว้นเพียงข้อเดียวที่สามารถจัดการได้โดยบน CATCH block


2

finallyบล็อกมักจะทำงาน ไม่ว่าคุณreturnจากภายในบล็อกลองหรือมีข้อยกเว้นเกิดขึ้น ข้อยกเว้นที่เกิดขึ้นในfinallyบล็อกจะแทนที่สิ่งที่ส่งออกไปในสาขาที่จับได้

นอกจากนี้การโยนข้อยกเว้นจะไม่ทำให้เกิดผลลัพธ์ใด ๆ ด้วยตัวเอง บรรทัดthrow new Exception("2");จะไม่เขียนอะไรเลย


1
ใช่ฉันรู้ว่าการขว้าง Exception ออกไปโดยตัวมันเอง แต่ฉันไม่เห็นเหตุผลว่าทำไม Exception 2 ควรจะลดลง ฉันฉลาดขึ้นอีกเล็กน้อย :-)
Kousalik

มักจะเป็นเวลานานมากและในสิ่งที่เวลานานมากที่สามารถเกิดขึ้น (ปริศนาเช็คwouter.coekaerts.be/2012/puzzle-dreams )
Dainius

0

ตามรหัสของคุณ:

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());
}

คุณสามารถดูได้ที่นี่:

  1. พิมพ์และโยนข้อยกเว้น# 1;
  2. ข้อยกเว้นนี้ได้ติดตามคำสั่งจับและพิมพ์B - # 2;
  3. ในที่สุดบล็อกก็# 3จะดำเนินการหลังจากลองจับ (หรือลองถ้าไม่เกิดข้อยกเว้นใด ๆ ) คำสั่งและพิมพ์C - # 4และโยนข้อยกเว้นใหม่;
  4. คนนี้ได้จับตามคำสั่งจับภายนอก# 5;

ABC3ผลคือ และ2ถูกละไว้ในลักษณะเดียวกับ1


ขออภัยไม่ได้ยกเว้น ("1") แต่สามารถตรวจจับได้สำเร็จ
Black Maggie

@Black Maggie แคชและส่งข้อยกเว้นใหม่ => นี่ไม่ใช่แคชและโปรแกรมถูกยกเลิก และก่อนที่บล็อกนี้จะถูกเรียกใช้งานในที่สุด
nazar_art
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.