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

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

     จากตัวอย่างด้านล่างเมธอด preventiveErr() เป็นการป้องกันไม่เกิดข้อผิดพลาด สังเกตุจากบรรทัดที่  13  ที่เราตรวจสอบค่า j ก่อนนำไปใช้งาน ส่วนเมธอด correctnessErr() เป็นการจัดการเมื่อเกิดข้อผิดพลาดโดยใช้รูปแบบ  try-catch-finally ซึ่งจะกล่าวถึงต่อไป

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

      คลาสข้อผิดพลาดจะเป็นคลาสลูกที่สืบทอดมาจากคลาส Throwable จากภาพจะเห็นว่าคลาสบนสุดคือ java.lang.Throwable ซึ่งจะมีเมธอดพื้นฐานที่จะถูกสืบทอดไปยังคลาสลูก เช่น เมธอด getMessage() ซึ่งคืนค่าเป็น String แสดงรายละเอียดของข้อผิดพลาด, เมธอด getCause() ซึ่งคืนค่าเป็นออบเจกต์ของคลาส Throwable หรือออบเจกต์ของคลาสลูกที่สืบทอดมาจากคลาส Throwable ซึ่งเป็นสาเหตุของข้อผิดพลาด และ เมธอด printStackTrace() เพื่อพิมพ์ข้อผิดพลาดในรูปแบบมาตรฐานของ stream ข้อผิดพลาด (standard error stream) เป็นต้น คลาส Throwable มีคลาสลูก 2 คลาสคือคลาส java.lang.Error และคลาส java.lang.Exception โดยคลาส Error เป็นข้อผิดพลาดแบบ unchecked exception และมีคลาสลูกที่เป็นเรื่องของข้อผิดพลาดในระดับล่าง (low-level exception) ของ Java Virtual Machine ซึ่งเป็นข้อผิดพลาดที่เกิดขึ้นนอกขอบเขตของโปรแกรมที่เราเขียน ตัวอย่างคลาสลูกที่สืบทอดจากคลาส Error เช่น คลาส OutOfMemoryError, คลาส StackOverflowError เป็นต้น ส่วนคลาส Exception จะมีคลาสลูกเป็นเรื่องของข้อผิดพลาดที่เกิดจากโปรแกรมที่เราเขียน ตัวอย่างคลาสลูกที่สืบทอดจากคลาส Exception เช่น คลาส RuntimeExection, คลาส IOException เป็นต้น คลาสลูกของคลาส Exception จะเป็นข้อผิดพลาดแบบ checked exception ยกเว้น คลาส RuntimeException จะเป็นข้อผิดพลาดแบบ unchecked exception ซึ่งคลาส RuntimeException จะมีคลาสลูก เช่น คลาส ArithmeticException, คลาส NumberFormatException, คลาส NullPointerException เป็นต้น

     ข้อผิดพลาดจะแบ่งออกเป็น 2 ชนิดคือ checked exceptions และ unchecked exceptions โดย checked exceptions เป็นข้อผิดพลาดที่รู้อยู่แล้วว่ามีโอกาสที่จะเกิด ส่วน unchecked exceptions เป็นข้อผิดพลาดที่สามารถเกิดได้ในขณะที่ใช้งานโปรแกรม 

     สำหรับข้อผิดพลาดชนิด checked exceptions โปรแกรม IntelliJ IDE จะบังคับให้เราต้องกำหนดการแจ้งข้อผิดพลาดไว้ในเมธอดที่เราสร้างขึ้นมาเพื่อที่เวลาเกิดข้อผิดพลาดโปรแกรมของเราจะได้ส่งรายละเอียดข้อผิดพลาดที่เกิดขึ้นกลับไปให้โปรแกรมที่เรียกใช้งาน ตัวอย่างของ checked exceptions เช่น คลาส NoSuchFieldException, คลาส FileNotFoundException, คลาส InterruptedException, คลาส NoSuchMethodException, คลาส ClassNotFoundException, คลาส ParseException, คลาส CloneNotSupportedException, คลาส Instantiation Exception เป็นต้น

     ตัวย่างของข้อผิดพลาดชนิด unchecked exceptions เช่น คลาส ArithmeticException, คลาส NullPointerException, คลาส ArrayIndexOutofBoundsException, คลาส UnsupportedOperationException, คลาส SecurityException, คลาส SystemException, คลาส MissingResourceException, คลาส NoSuchElementException, คลาส UndeclaredThrowableException, คลาส EmptyStackException เป็นต้น

     ก่อนจะทำความเข้าใจการทำงานของข้อผิดพลาด เราจะมาทำความเข้าใจว่าภาษาจาวาจดจำลำดับการทำงานของโปรแกรมอย่างไรก่อน สมมุติว่าเราเขียนโปรแกรมดังตัวอย่างด้านล่าง

     จากตัวอย่างด้านบนโปรแกรมเริ่มต้นจากเมธอด main() ซึ่งจะไปเรียกเมธอด method1() ซึ่งไปเรียก method2() อีกทีหนึ่ง ภาษาจาวาจะใช้แสต็ก (stack) ซึ่งเป็นโครงสร้างข้อมูลรูปแบบหนึ่งในการเก็บลำดับการทำงานดังภาพด้านล่าง 

     โครงสร้างการเก็บข้อมูลของแสต็กคืออะไรที่ใส่ลงไปล่าสุดจะถูกดำเนินการก่อน (LIFO – Last In First Out) ซึ่งเมื่อเริ่มดำเนินการซึ่งเป็นการทำงานตามคำสั่งในเมธอด main() ดังนั้น เมธอด main() จะถูกใส่ลงไปในแสต็กก่อน จากนั้นเมื่อเมธอด method1() ถูกเรียกใช้งานจึงถูกใส่ตามลงไป และตามด้วยเมธอด method2() และเมื่อดำเนินการเมธอด method2() เสร็จแล้วก็จะถอน เมธอด method2() ออกจากแสต็กแล้วไปทำเมธอด method1() และเมื่อดำเนินการเมธอด method1() เสร็จแล้วก็จะถอน เมธอด method1() ออกจากแสต็กแล้วไปทำเมธอด main() 

     ในภาษาจาวาเมื่อมีข้อผิดพลาดเกิดขึ้นจะมีการสร้างออบเจกต์ของข้อผิดพลาดขึ้นมาเพื่อแจ้งกับ java runtime ซึ่ง java runtime จะค้นหาการจัดการข้อผิดพลาดไปตามลำดับในแสต็ก หากพบการจัดการข้อผิดพลาดแล้ว ข้อผิดพลาดก็จะได้รับการจัดการตามที่กำหนดไว้ มิฉะนั้นโปรแกรมจะหยุดดำเนินการ

     จากตัวอย่างลำดับการทำงานด้านบน หากภาษาจาวาพบข้อผิดพลาดที่เมธอด method2() ภาษาจาวาจะมองหาการจัดการข้อผิดพลาดจากเมธอด method2() หากไม่พบจะไปมองหาจาก เมธอด method1() และหากไม่พบก็จะไปมองหาจากเมธอด main() หากไม่พบก็จะหยุดการทำงานของโปรแกรม

     ดังนั้นเราต้องดักจับหรือกำหนดการแจ้งข้อผิดพลาดและจัดเตรียมการจัดการข้อผิดพลาดสำหรับโปรแกรมของเรา โดยหากเป็นข้อผิดพลาดชนิด  expected exception เราจะต้องกำหนดการแจ้งข้อผิดพลาดในเมธอดที่อาจจะเกิดข้อผิดพลาดเลย โดยกำหนดในบรรทัดการประกาศเมธอดตั้งแต่ขั้นตอนการเขียนโปรแกรม ไม่เช่นนั้นจะคอมไพล์โปรแกรมไม่ผ่าน โดยใช้คีย์เวิร์ด throws ตามด้วยชื่อคลาสข้อผิดพลาดที่ต้องการ ซึ่งเมื่อโปรแกรมเกิดข้อผิดพลาด ออบเจกต์ของข้อผิดพลาดตามที่กำหนดจะถูกส่งกลับไปยังโปรแกรมที่เรียกใช้โปรแกรมที่เกิดข้อผิดพลาด (อาจจะจำง่ายๆว่า throws คือโยนข้อผิดพลาดกับไปหาผู้เรียก) ดังตัวอย่างด้านล่าง 

public void writeList() throws IOException {

     ถึงแม้ว่าเราจะจำไม่ได้ว่าข้อผิดพลาดแบบใดที่เป็น expected exception  หากเราใช้โปรแกรม IntelliJ IDE โปรแกรมจะช่วยเตือนเราในเรื่องนี้ 

     สำหรับข้อผิดพลาดชนิด unexpected exception นั้นเราสามารถไปดักจับหรือกำหนดการแจ้งข้อผิดพลาดที่เมธอดที่เรียกใช้เมธอดที่เกิดข้อผิดพลาดก็ได้ (calling method) โดยการดักจับหรือกำหนดการแจ้งข้อผิดพลาดและจัดเตรียมตัวจัดการข้อผิดพลาดทำได้โดยใช้ try – catch – finally บล็อก โดยมีรูปแบบดังตัวอย่างด้านล่าง

try {

     // โปรแกรมส่วนที่อาจจะเกิดข้อผิดพลาด

} catch (SQLException | IOException e) {

     // การจัดการในกรณีที่เป็น SQLException หรือ  IOException 

     throw e;

} finally {

    //โปรแกรมส่วนที่จะถูกทำงานเสมอไม่ว่าจะมีข้อผิดพลาดหรือไม่

}

     จะเห็นว่าการใช้  try – catch – finally บล็อก เป็นการใช้บล็อก try ห่อหุ้มโค้ดที่อาจจะเกิดข้อผิดพลาดไว้ในขณะที่บล็อก catch จะเป็นการจัดการกับข้อผิดพลาดที่เกิดขึ้น เช่น โยนข้อผิดพลาดกับขึ้นไปยังเมธอดที่เป็นผู้เรียก

try {

     // โปรแกรมส่วนที่อาจจะเกิดข้อผิดพลาด

} catch (RuntimeException e) {

     throw e; // โยนออบเจกต์ข้อผิดพลาดต่อไป

} finally {

     //โปรแกรมส่วนที่จะถูกทำงานเสมอไม่ว่าจะมีข้อผิดพลาดหรือไม่

}

     เราสามารถกำหนดการแสดงข้อผิดพลาดได้เอง (เขียนโปรแกรมสั่งให้โยน

ออบเจกต์ข้อผิดพลาดกลับไปเอง ไม่ใช่เกิดจาการดำเนินการโดยภาษาจาวา) โดยใช้คีย์เวิร์ด throw โดยเราต้องสร้างออบเจกต์จากคลาสข้อผิดพลาดที่ต้องการขึ้นมาก่อน และใช้คีย์เวิร์ด throw ในการส่งออบเจกต์ข้อผิดพลาดกลับไปตามลำดับของแสต็ก ดังตัวอย่างด้านล่าง 

RuntimeException e = new RuntimeException(“error description”);

throw e;

     อย่างไรก็ตามในการใช้งานโดยมากจะเขียนอยู่ในบรรทัดเดียว ดังตัวอย่าง

throw new Throwable(“Something’s bad.”); // เป็นการ throw ออบเจกต์ของคลาส Throwable

throw new Exception(“An exception occurs”); // เป็นการ throw ออบเจกต์ของคลาส Exception

throw new NullPointerException(“The field is null”); // เป็นการ throw ออบเจกต์ของคลาส NullPointerException

     ข้อดีของการกำหนดการแสดงข้อผิดพลาดเองคือเราสามารถอธิบายสาเหตุของการเกิดข้อผิดพลาดได้อย่างชัดเจนซึ่งจะดีต่อการเขียนข้อผิดพลาดในไฟล์ log เพื่อสืบสวนหาสาเหตุในภายหลัง เช่น 

throw new ArithmeticException (“trying to divided by zero ”);

     เราสามารถใช้ try-catch-finally เพื่อจัดการกับ exception หลายๆแบบพร้อมกันได้ดังตัวอย่าง

try {

    // โปรแกรมส่วนที่อาจจะเกิดข้อผิดพลาด

} catch (SQLException | IOException e) {

    // การจัดการในกรณีที่เป็น SQLException หรือ  IOException 

} catch (Exception e) {

    // การจัดการในกรณีที่่ไม่ระบุข้อผิดพลาดแบบเฉพาะเจาะจง

} finally {

    //โปรแกรมส่วนที่จะถูกทำงานเสมอไม่ว่าจะมีข้อผิดพลาดหรือไม่

}

     บางครั้งเราอาจจะไม่ต้องจัดเตรียมการจัดการกับข้อผิดพลาดที่เกิดขึ้นหากคลาสหรือแพคเกจของเราจะถูกเรียกใช้โดยโปรแกรมอื่นซึ่งผู้พัฒนาโปรแกรมนั้นๆจะจัดเตรียมการจัดการกับข้อผิดพลาดเอง แต่เราจะต้องแจ้งข้อผิดพลาดและสาเหตุให้ชัดเจน

     นอกจากการใช้ try-catch-finally บล็อกสำหรับการดักจับข้อผิดพลาดแล้ว เราสามารถใช้ try-with-resource บล็อกเพื่อดักจับข้อผิดพลาดได้ด้วยเช่นกัน ตัวอย่างเช่น ในการใช้งานออบเจกต์ที่ต้องมีการสั่งปิดออบเจกต์เพื่อคืนทรัพยากรอย่างเช่น การดำเนินการกับไฟล์ หากเราใช้ try-catch-finally บล็อกเราจะกำหนดการปิดออบเจกต์ที่บล็อก finally เสมอเพื่อให้แน่ใจว่าไม่ว่าอย่างไรออบเจกต์จะต้องถูกปิด แต่ก็มีโอกาศเกิด IOException ได้ในขั้นตอนการปิด เราเลยต้องซ้อน try-catch-finally เข้าไปภายใต้บล็อก finally อีกดังตัวอย่างด้านล่าง

    ซึ่งในกรณีนี้เราสามารถใช้ try-with-resource บล็อกซึ่งจะการันตีว่าออบเจกต์จะถูกปิดเสมอ และทำให้โปรแกรมดูอ่านง่าย กว่าดังตัวอย่าง

     นอกจากการแบ่ง exception เป็น checked exception หรือ unchecked exception แล้ว ยังสามารถแบ่งได้จากส่วนของโปรแกรมที่ทำให้เกิดข้อผิดพลาดโดยแบ่งออกเป็น synchronous delivery และ asynchronous delivery  โดย synchronous delivery คือข้อผิดพลาดที่สามารถเกิดขึ้นได้จากบางคำสั่งอย่างฉพาะเจาะจง เช่น การใช้งานอาเรย์จะมีโอกาสเกิดข้อผิดพลาดที่เกี่ยวกับอาเรย์แน่ๆ ตัวอย่างเช่น NullPointerException,ArrayIndexOutOfBoundException ส่วน asynchronous delivery คือข้อผิดพลาดที่สามารถเกิดขึ้นจากส่วนใดของโปรแกรมก็ได้อย่างไม่เฉพาะเจาะจง ตัวอย่างเช่น StackOverflowError OutOfMemoryError

     ตัวอย่างด้านล่างเป็น การแสดงให้เห็นการใช้งาน Exception Handling ในภาพรวม โดยเมธอด main จะเรียกใช้เมธอด rs ซึ่งเรียกใช้เมธอด dv อีกทีหนึ่ง เมื่อเกิดการหารด้วย 0 ตามคำสั่งในบรรทัดที่ 3 java runtime จะสร้างออบเจกต์ข้อผิดพลาดขึ้นมาซึ่งจะถูกดักจับได้จากบรรทัดที่ 15 จากนั้นในบรรทัดที่ 16 เราโยนออบเจกต์ต่อให้เมธอด sr ซึ่งเป็นผู้เรียก ออบเจกต์ข้อผิดพลาดดังกล่าวถูกดักจับได้จากบรรทัดที่ 24 และในบรรทัดที่ 25 สั่งพิมพ์รายละเอียดจากออบเจกต์คือ “/ by zero” ซึ่งเป็นข้อความมาตรฐานของออบเจกต์ แสดงว่าเป็นออบเจกต์ที่ถูกสร้างขึ้นมาโดย java runtime จากนั้นในบรรทัดที่ 26 เราสร้างออบเจกต์ข้อผิดพลาดของเราเองพร้อมทั้งระบุคำอธิบายตามที่ต้องการและโยนต่อไปยังผู้เรียกซึ่งจะถูกดักจับได้ในบรรทัดที่ 4 และเมื่อพิมพ์รายละเอียดออกมาจะได้ “Error description throw from class SowResult” ซึ่งยืนยันว่าเป็นออบเจกต์ข้อผิดพลาดที่มาจากเมธอด sr และเมื่อมีการดักจับและจัดการข้อผิดพลาดโปรแกรมสามารถทำงานได้ต่อไปจนจบโดยการพิมพ์ข้อความ “Your program can run normally”

      สำหรับเมธอด dv อาจจะเขียนอีกแบบโดยไม่ใช้ try-catch-finally บล็อกได้ดังตัวอย่างด้านล่าง