สวัสดี! ฉันเกลียดที่จะทำลายมันให้คุณ แต่งานส่วนใหญ่ของโปรแกรมเมอร์คือการจัดการกับข้อผิดพลาด และบ่อยที่สุด - ด้วยตัวเอง มันเกิดขึ้นจนไม่มีใครที่ไม่ทำผิดพลาด และไม่มีโปรแกรมดังกล่าวเช่นกัน แน่นอนว่าสิ่งสำคัญเมื่อแก้ไขข้อผิดพลาดคือการเข้าใจสาเหตุของข้อผิดพลาด และอาจมีสาเหตุหลายประการในโปรแกรม จนถึงจุดหนึ่งผู้สร้าง 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
Exception in thread "main"
เอ่อเอ่อ :/ ไม่มีอะไรชัดเจน ข้อผิดพลาดประเภทใดและมาจากไหนไม่ชัดเจน ไม่มีข้อมูลที่เป็นประโยชน์ แต่ด้วยคลาสที่หลากหลายโปรแกรมเมอร์จึงได้รับสิ่งสำคัญสำหรับตัวเอง - ประเภทของข้อผิดพลาดและสาเหตุที่น่าจะเป็นไปได้ซึ่งมีอยู่ในชื่อของคลาส ท้ายที่สุดแล้ว การเห็นในคอนโซลจะแตกต่างไปจากเดิมอย่างสิ้นเชิง:
Exception in thread "main" java.io.FileNotFoundException: C:\Users\Username\Desktop\test.txt (Системе не удается найти указанный путь)
ชัดเจนทันทีว่าปัญหาคืออะไร และ “จะขุดไปในทิศทางไหน” เพื่อแก้ไขปัญหา! ข้อยกเว้น เช่นเดียวกับอินสแตนซ์อื่นๆ ของคลาส คืออ็อบเจ็กต์
การจับและการจัดการข้อยกเว้น
ในการทำงานกับข้อยกเว้นใน Java มีบล็อกโค้ดพิเศษ:try
และcatch
. รหัสที่โปรแกรมเมอร์คาดว่าจะเกิดข้อยกเว้นจะถูกวางไว้ในบล็อก นี่ไม่ได้หมายความว่าจะต้องมีข้อยกเว้นเกิดขึ้นที่ตำแหน่งนี้ ซึ่งหมายความว่ามันสามารถเกิดขึ้นที่นั่นได้ และโปรแกรมเมอร์ก็ตระหนักถึงมัน ประเภทของข้อผิดพลาดที่คุณคาดว่าจะได้รับจะถูกวางไว้ในบล็อก(“catch”) นี่คือที่ที่โค้ดทั้งหมดที่จำเป็นต้องดำเนินการหากมีข้อยกเว้นเกิดขึ้น นี่คือตัวอย่าง: finally
try
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 (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-finally
triumvirate ลักษณะเฉพาะของมันคือ มันถูกดำเนินการภายใต้สถานการณ์การทำงานของโปรแกรมใดๆ
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();
}
}
}
ตอนนี้เรามั่นใจอย่างยิ่งว่าเราได้ดูแลทรัพยากรที่ถูกครอบครองแล้ว ไม่ว่าจะเกิดอะไรขึ้นในขณะที่โปรแกรมกำลังทำงาน :) นั่นไม่ใช่ทั้งหมดที่คุณต้องรู้เกี่ยวกับข้อยกเว้น การจัดการข้อผิดพลาดเป็นหัวข้อที่สำคัญมากในการเขียนโปรแกรม: มีบทความมากกว่าหนึ่งบทความ ในบทถัดไป เราจะเรียนรู้ว่ามีข้อยกเว้นประเภทใดบ้าง และวิธีสร้างข้อยกเว้นของคุณเอง :) แล้วพบกัน!
GO TO FULL VERSION