JavaRush /จาวาบล็อก /Random-TH /ข้อยกเว้นใน Java: การจับและการจัดการ

ข้อยกเว้นใน Java: การจับและการจัดการ

เผยแพร่ในกลุ่ม
สวัสดี! ฉันเกลียดที่จะทำลายมันให้คุณ แต่งานส่วนใหญ่ของโปรแกรมเมอร์คือการจัดการกับข้อผิดพลาด และบ่อยที่สุด - ด้วยตัวเอง มันเกิดขึ้นจนไม่มีใครที่ไม่ทำผิดพลาด และไม่มีโปรแกรมดังกล่าวเช่นกัน แน่นอนว่าสิ่งสำคัญเมื่อแก้ไขข้อผิดพลาดคือการเข้าใจสาเหตุของข้อผิดพลาด และอาจมีสาเหตุหลายประการในโปรแกรม จนถึงจุดหนึ่งผู้สร้าง Java ต้องเผชิญกับคำถาม: จะทำอย่างไรกับข้อผิดพลาดที่อาจเกิดขึ้นในโปรแกรมเหล่านี้? การหลีกเลี่ยงสิ่งเหล่านั้นโดยสิ้นเชิงนั้นไม่สมจริง โปรแกรมเมอร์สามารถเขียนสิ่งที่เป็นไปไม่ได้ที่จะจินตนาการ :) ซึ่งหมายความว่าจำเป็นต้องสร้างกลไกในการจัดการกับข้อผิดพลาดในภาษา กล่าวอีกนัยหนึ่งหากเกิดข้อผิดพลาดในโปรแกรม จำเป็นต้องมีสคริปต์เพื่อการทำงานต่อไป โปรแกรมควรทำอย่างไรเมื่อเกิดข้อผิดพลาด? วันนี้เราจะมาทำความรู้จักกับกลไกนี้กัน และเรียกว่า “ข้อยกเว้น

ข้อยกเว้นใน Java คืออะไร

ข้อยกเว้นคือสถานการณ์พิเศษที่ไม่ได้วางแผนไว้บางประการที่เกิดขึ้นระหว่างการทำงานของโปรแกรม อาจมีตัวอย่างข้อยกเว้นมากมายใน Java ตัวอย่างเช่น คุณเขียนโค้ดที่อ่านข้อความจากไฟล์และแสดงบรรทัดแรกบนคอนโซล
public class Main {

   public static void main(String[] args) throws IOException {
       BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
       String firstString = reader.readLine();
       System.out.println(firstString);
   }
}
แต่ไม่มีไฟล์ดังกล่าว! ผลลัพธ์ของโปรแกรมจะเป็นข้อยกเว้น - FileNotFoundException. บทสรุป:

Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (Системе не удается найти указанный путь)
แต่ละข้อยกเว้นจะแสดงโดยคลาสที่แยกจากกันใน Java Throwableคลาสข้อยกเว้นทั้งหมดมาจาก "บรรพบุรุษ " ทั่วไป - คลาสพาเรนต์ ชื่อของคลาสข้อยกเว้นมักจะสะท้อนถึงเหตุผลของการเกิดขึ้นโดยย่อ:
  • FileNotFoundException(ไม่พบไฟล์)
  • ArithmeticException(ยกเว้นเมื่อดำเนินการทางคณิตศาสตร์)
  • ArrayIndexOutOfBoundsException(ระบุจำนวนเซลล์อาร์เรย์เกินความยาว) ตัวอย่างเช่น หากคุณพยายามแสดงอาร์เรย์ของเซลล์[23] ไปยังคอนโซลสำหรับอาร์เรย์อาร์เรย์ที่มีความยาว 10
มีคลาสดังกล่าวเกือบ 400 คลาสใน Java! ทำไมมากมาย? แม่นยำเพื่อให้โปรแกรมเมอร์ทำงานร่วมกับพวกเขาได้สะดวกยิ่งขึ้น ลองนึกภาพ: คุณเขียนโปรแกรม และเมื่อมันทำงาน มันจะส่งข้อยกเว้นที่มีลักษณะดังนี้:
Exception in thread "main"
เอ่อเอ่อ :/ ไม่มีอะไรชัดเจน ข้อผิดพลาดประเภทใดและมาจากไหนไม่ชัดเจน ไม่มีข้อมูลที่เป็นประโยชน์ แต่ด้วยคลาสที่หลากหลายโปรแกรมเมอร์จึงได้รับสิ่งสำคัญสำหรับตัวเอง - ประเภทของข้อผิดพลาดและสาเหตุที่น่าจะเป็นไปได้ซึ่งมีอยู่ในชื่อของคลาส ท้ายที่สุดแล้ว การเห็นในคอนโซลจะแตกต่างไปจากเดิมอย่างสิ้นเชิง:
Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (Системе не удается найти указанный путь)
ชัดเจนทันทีว่าปัญหาคืออะไร และ “จะขุดไปในทิศทางไหน” เพื่อแก้ไขปัญหา! ข้อยกเว้น เช่นเดียวกับอินสแตนซ์อื่นๆ ของคลาส คืออ็อบเจ็กต์

การจับและการจัดการข้อยกเว้น

ในการทำงานกับข้อยกเว้นใน Java มีบล็อกโค้ดพิเศษ: tryและcatch. รหัสที่โปรแกรมเมอร์คาดว่าจะเกิดข้อยกเว้นจะถูกวางไว้ในบล็อก นี่ไม่ได้หมายความว่าจะต้องมีข้อยกเว้นเกิดขึ้นที่ตำแหน่งนี้ ซึ่งหมายความว่ามันสามารถเกิดขึ้นที่นั่นได้ และโปรแกรมเมอร์ก็ตระหนักถึงมัน ประเภทของข้อผิดพลาดที่คุณคาดว่าจะได้รับจะถูกวางไว้ในบล็อก(“catch”) นี่คือที่ที่โค้ดทั้งหมดที่จำเป็นต้องดำเนินการหากมีข้อยกเว้นเกิดขึ้น นี่คือตัวอย่าง: finallyข้อยกเว้น: การสกัดกั้นและการประมวลผล - 2trycatch
public static void main(String[] args) throws IOException {
   try {
       BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));

       String firstString = reader.readLine();
       System.out.println(firstString);
   } catch (FileNotFoundException e) {

       System.out.println("Error! File not found!");
   }
}
บทสรุป:

Ошибка! Файл не найден!
เราใส่โค้ดของเราไว้ในสองช่วงตึก ในบล็อกแรกเราคาดว่าอาจเกิดข้อผิดพลาด "ไม่พบไฟล์" tryนี่ คือบล็อก ในส่วนที่สอง เราจะบอกโปรแกรมว่าต้องทำอย่างไรหากเกิดข้อผิดพลาด นอกจากนี้ยังมีข้อผิดพลาดประเภทเฉพาะ - FileNotFoundException. หากเราส่งcatchคลาสข้อยกเว้นอื่นเข้าไปในวงเล็บบล็อก มันจะไม่ถูกตรวจจับ
public static void main(String[] args) throws IOException {
   try {
       BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));
       String firstString = reader.readLine();
       System.out.println(firstString);
   } catch (ArithmeticException e) {

       System.out.println("Error! File not found!");
   }
}
บทสรุป:

Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (Системе не удается найти указанный путь)
รหัสในบล็อกcatchไม่ทำงานเนื่องจากเรา "กำหนดค่า" บล็อกนี้ให้สกัดกั้นArithmeticExceptionและรหัสในบล็อกtryก็โยนประเภทอื่นออกมาFileNotFoundException- เราไม่ได้เขียนสคริปต์ สำหรับFileNotFoundExceptionดังนั้นโปรแกรมจึงแสดงในคอนโซลข้อมูลที่แสดงตามค่าเริ่มต้นFileNotFoundExceptionสำหรับ ที่นี่คุณต้องใส่ใจกับ 3 สิ่ง อันดับแรก. ทันทีที่มีข้อยกเว้นเกิดขึ้นในบรรทัดของโค้ดใดๆ ใน try block โค้ดหลังจากนั้นจะไม่ถูกดำเนินการอีกต่อไป การทำงานของโปรแกรมจะ "กระโดด" ไปที่บล็อกcatchทันที ตัวอย่างเช่น:
public static void main(String[] args) {
   try {
       System.out.println("Divide a number by zero");
       System.out.println(366/0);//this line of code will throw an exception

       System.out.println("This");
       System.out.println("code");
       System.out.println("Not");
       System.out.println("will");
       System.out.println("done!");

   } catch (ArithmeticException e) {

       System.out.println("The program jumped to the catch block!");
       System.out.println("Error! You can't divide by zero!");
   }
}
บทสรุป:

Делим число на ноль 
Программа перепрыгнула в блок catch! 
Ошибка! Нельзя делить на ноль! 
ในบล็อก บรรทัดที่ สองtryเราพยายามหารตัวเลขด้วย 0 ซึ่งส่งผลให้เกิดข้อยกเว้น ArithmeticExceptionหลังจากนี้ บรรทัดที่ 6-10 ของบล็อกtryจะไม่ถูกดำเนินการอีกต่อไป ดังที่เราได้กล่าวไปแล้ว โปรแกรมก็เริ่มดำเนินการบล็อกcatchทันที ที่สอง. catchสามารถมีได้หลาย ช่วง ตึก หากโค้ดในบล็อกtryไม่สามารถโยนข้อยกเว้นได้หลายประเภท คุณสามารถเขียนบล็อกของคุณเองสำหรับแต่ละบล็อกcatchได้
public static void main(String[] args) throws IOException {
   try {
       BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));

       System.out.println(366/0);
       String firstString = reader.readLine();
       System.out.println(firstString);
   } catch (FileNotFoundException e) {

       System.out.println("Error! File not found!");

   } catch (ArithmeticException e) {

       System.out.println("Error! Division by 0!");

   }
}
ในตัวอย่างนี้เราเขียนสองช่วงcatchตึก หากtryเกิดขึ้น ในบล็อก FileNotFoundExceptionบล็อกแรกจะถูกดำเนินcatchการ หากเกิดขึ้นArithmeticExceptionอันที่สองจะถูกดำเนินการ คุณสามารถเขียนได้อย่างน้อย 50 บล็อกcatchแต่แน่นอนว่า เป็นการดีกว่าที่จะไม่เขียนโค้ดที่อาจทำให้เกิดข้อผิดพลาดได้ 50 ประเภท :) ประการที่สาม คุณจะรู้ได้อย่างไรว่าโค้ดของคุณอาจมีข้อยกเว้นอะไรบ้าง แน่นอนว่าคุณสามารถเดาบางอย่างได้ แต่มันเป็นไปไม่ได้ที่จะเก็บทุกอย่างไว้ในหัว ดังนั้นคอมไพเลอร์ Java จึงรู้เกี่ยวกับข้อยกเว้นที่พบบ่อยที่สุด และรู้ว่าข้อยกเว้นเหล่านี้สามารถเกิดขึ้นได้ในสถานการณ์ใดบ้าง ตัวอย่างเช่น หากคุณเขียนโค้ดและคอมไพเลอร์รู้ว่าอาจมีข้อยกเว้น 2 ประเภทเกิดขึ้นระหว่างการดำเนินการ โค้ดของคุณจะไม่คอมไพล์จนกว่าคุณจะจัดการมัน เราจะดูตัวอย่างด้านล่างนี้ ตอนนี้เกี่ยวกับการจัดการข้อยกเว้น มี 2 ​​วิธีในการประมวลผล catch()เราได้พบวิธีแรกแล้ว - วิธีการสามารถจัดการข้อ ยกเว้นได้อย่างอิสระในบล็อก มีตัวเลือกที่สอง - วิธีการนี้สามารถส่งข้อยกเว้นขึ้นบน call stack มันหมายความว่าอะไร? ตัวอย่างเช่น ในชั้นเรียนของเรา เรามีเมธอดแบบเดียวกันprintFirstString()ที่อ่านไฟล์และแสดงบรรทัดแรกบนคอนโซล:
public static void printFirstString(String filePath) {

   BufferedReader reader = new BufferedReader(new FileReader(filePath));
   String firstString = reader.readLine();
   System.out.println(firstString);
}
ขณะนี้โค้ดของเราไม่สามารถคอมไพล์ได้เนื่องจากมีข้อยกเว้นที่ไม่สามารถจัดการได้ ในบรรทัดที่ 1 คุณระบุเส้นทางไปยังไฟล์ คอมไพเลอร์รู้ว่าโค้ดดังกล่าวสามารถนำไปสู่ไฟล์FileNotFoundException. ในบรรทัดที่ 3 คุณอ่านข้อความจากไฟล์ ในกระบวนการนี้IOExceptionอาจเกิดข้อผิดพลาดได้ง่ายระหว่างข้อมูลอินพุต-เอาท์พุต (Input-Output) ตอนนี้คอมไพเลอร์กำลังบอกคุณว่า“เพื่อน ฉันจะไม่อนุมัติโค้ดนี้หรือคอมไพล์มันจนกว่าคุณจะบอกฉันว่าฉันควรทำอย่างไรหากมีข้อยกเว้นข้อใดข้อหนึ่งเกิดขึ้น และสิ่งเหล่านี้สามารถเกิดขึ้นได้อย่างแน่นอนตามโค้ดที่คุณเขียน!” . ไม่มีที่ไหนให้ไปคุณต้องดำเนินการทั้งสองอย่าง! ตัวเลือกการประมวลผลแรกนั้นเราคุ้นเคยอยู่แล้ว: เราต้องวางโค้ดของเราในบล็อกtryและเพิ่มสองบล็อกcatch:
public static void printFirstString(String filePath) {

   try {
       BufferedReader reader = new BufferedReader(new FileReader(filePath));
       String firstString = reader.readLine();
       System.out.println(firstString);
   } catch (FileNotFoundException e) {
       System.out.println("Error, file not found!");
       e.printStackTrace();
   } catch (IOException e) {
       System.out.println("Error while inputting/outputting data from file!");
       e.printStackTrace();
   }
}
แต่นี่ไม่ใช่ทางเลือกเดียว เราสามารถหลีกเลี่ยงการเขียนสคริปต์สำหรับข้อผิดพลาดภายในเมธอดได้ และเพียงแค่โยนข้อยกเว้นไว้ด้านบนสุด ทำได้โดยใช้คีย์เวิร์ดthrowsซึ่งเขียนไว้ในการประกาศเมธอด:
public static void printFirstString(String filePath) throws FileNotFoundException, IOException {
   BufferedReader reader = new BufferedReader(new FileReader(filePath));
   String firstString = reader.readLine();
   System.out.println(firstString);
}
หลังจากคำนั้นthrowsเราจะแสดงรายการข้อยกเว้นทุกประเภทที่วิธีนี้สามารถใช้ได้ระหว่างการดำเนินการโดยคั่นด้วยเครื่องหมายจุลภาค เหตุใดจึงทำเช่นนี้? ตอนนี้ หากใครบางคนในโปรแกรมต้องการเรียกใช้เมธอดprintFirstString()เขาจะต้องดำเนินการจัดการข้อยกเว้นด้วยตัวเอง ตัวอย่างเช่น ในอีกส่วนหนึ่งของโปรแกรม เพื่อนร่วมงานคนหนึ่งของคุณเขียนเมธอดโดยให้เรียกเมธอดของคุณว่าprintFirstString():
public static void yourColleagueMethod() {

   //...your colleague's method does something

   //...and at one moment calls your printFirstString() method with the file it needs
   printFirstString("C:\\Users\\Eugene\\Desktop\\testFile.txt");
}
เกิดข้อผิดพลาด รหัสไม่คอมไพล์! printFirstString()เราไม่ได้เขียนสคริปต์การจัดการข้อผิดพลาดในเมธอด ดังนั้นงานจึงตกเป็นภาระของผู้ที่จะใช้วิธีนี้ นั่นคือตอนนี้วิธีyourColleagueMethod()การเผชิญกับ 2 ตัวเลือกที่เหมือนกัน: จะต้องประมวลผลทั้งสองข้อยกเว้นที่ "บิน" ไปที่มันโดยใช้try-catchหรือส่งต่อเพิ่มเติม
public static void yourColleagueMethod() throws FileNotFoundException, IOException {
   //...the method does something

   //...and at one moment calls your printFirstString() method with the file it needs
   printFirstString("C:\\Users\\Eugene\\Desktop\\testFile.txt");
}
ในกรณีที่สอง การประมวลผลจะตกอยู่บนไหล่ของวิธีการถัดไปบนสแต็ก - วิธีที่จะเรียกyourColleagueMethod(). นั่นคือสาเหตุที่กลไกดังกล่าวเรียกว่า "การโยนข้อยกเว้นขึ้น" หรือ "การส่งผ่านไปยังด้านบน" เมื่อคุณโยนข้อยกเว้นโดยใช้throwsโค้ดจะคอมไพล์ ในขณะนี้ ดูเหมือนว่าคอมไพเลอร์จะพูดว่า: "เอาล่ะ โอเค รหัสของคุณมีข้อยกเว้นที่อาจเกิดขึ้นมากมาย แต่ฉันจะรวบรวมมันต่อไป เราจะกลับมาที่การสนทนานี้!” และเมื่อคุณเรียกใช้เมธอดที่ไหนสักแห่งในโปรแกรมที่ไม่ได้จัดการกับข้อยกเว้น คอมไพลเลอร์จะปฏิบัติตามสัญญาและเตือนคุณเกี่ยวกับสิ่งเหล่านั้นอีกครั้ง สุดท้ายนี้เราจะพูดถึงเรื่องบล็อกfinally(ขออภัยที่เล่นสำนวน) นี่เป็นส่วนสุดท้ายของการจัดการข้อยกเว้นแบบtry-catch-finallytriumvirate ลักษณะเฉพาะของมันคือ มันถูกดำเนินการภายใต้สถานการณ์การทำงานของโปรแกรมใดๆ
public static void main(String[] args) throws IOException {
   try {
       BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));

       String firstString = reader.readLine();
       System.out.println(firstString);
   } catch (FileNotFoundException e) {
       System.out.println("Error! File not found!");
       e.printStackTrace();
   } finally {
       System.out.println("And here is the finally block!");
   }
}
ในตัวอย่างนี้ รหัสภายในบล็อกfinallyจะถูกดำเนินการในทั้งสองกรณี หากโค้ดในบล็อกtryถูกดำเนินการทั้งหมดและไม่มีข้อยกเว้น บล็อกจะเริ่มทำงานที่จุดfinallyสิ้นสุด หากโค้ดภายในtryถูกขัดจังหวะและโปรแกรมข้ามไปที่บล็อกcatchหลังจากที่โค้ดภายในถูกดำเนินการcatchแล้ว บล็อกนั้นจะยังคงถูกเลือกfinallyอยู่ เหตุใดจึงจำเป็น? วัตถุประสงค์หลักคือเพื่อดำเนินการส่วนที่จำเป็นของโค้ด ส่วนนั้นจะต้องทำให้เสร็จโดยไม่คำนึงถึงสถานการณ์ ตัวอย่างเช่น มักจะทำให้ทรัพยากรบางส่วนที่โปรแกรมใช้ว่าง ในโค้ดของเรา เราจะเปิดสตรีมเพื่ออ่านข้อมูลจากไฟล์และส่งต่อไปยังไฟล์BufferedReader. เราreaderต้องปิดตัวลงและปล่อยทรัพยากรให้ว่าง จะต้องดำเนินการนี้ไม่ว่าในกรณีใด: ไม่สำคัญว่าโปรแกรมจะทำงานตามที่คาดไว้หรือมีข้อยกเว้นเกิดขึ้นหรือไม่ สะดวกในการทำเช่นนี้ในบล็อกfinally:
public static void main(String[] args) throws IOException {

   BufferedReader reader = null;
   try {
       reader = new BufferedReader(new FileReader("C:\\Users\\Username\\Desktop\\test.txt"));

       String firstString = reader.readLine();
       System.out.println(firstString);
   } catch (FileNotFoundException e) {
       e.printStackTrace();
   } finally {
       System.out.println("And here is the finally block!");
       if (reader != null) {
           reader.close();
       }
   }
}
ตอนนี้เรามั่นใจอย่างยิ่งว่าเราได้ดูแลทรัพยากรที่ถูกครอบครองแล้ว ไม่ว่าจะเกิดอะไรขึ้นในขณะที่โปรแกรมกำลังทำงาน :) นั่นไม่ใช่ทั้งหมดที่คุณต้องรู้เกี่ยวกับข้อยกเว้น การจัดการข้อผิดพลาดเป็นหัวข้อที่สำคัญมากในการเขียนโปรแกรม: มีบทความมากกว่าหนึ่งบทความ ในบทถัดไป เราจะเรียนรู้ว่ามีข้อยกเว้นประเภทใดบ้าง และวิธีสร้างข้อยกเว้นของคุณเอง :) แล้วพบกัน!
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION