You are on page 1of 14

intro. to Java (FEU.

faa)
ในบทที่เจ็ดนี้เราจะพูดถึงการใช และการออกแบบ exception หรือที่เรียกวาการควบคุมและดัก
จับ error ที่อาจเกิดขึ้นในการ compile และ run โปรแกรม

หลังจากจบบทนี้แลว ผูอานจะไดทราบถึง

o ความหมายของ exception
o วิธีการใชและควบคุม exception
o การใช throws และ try
o การออกแบบ และใช exception ที่เขียนขึ้นเอง
o การใช assertion

7.1การตรวจสอบ error

ในภาษา Java นั้น exception เปนการบอกถึงสิ่งผิดปรกติที่เกิดขึ้นในโปรแกรม ซึ่งอาจเปน


error หรือเหตุการณที่ไมคอยเขาทาที่ตองการความดูแลเปนพิเศษ โดยทั่วไปในการเขียน
โปรแกรมที่ดีนั้น เราจะตองตรวจสอบถึงเหตุการณที่อาจทําใหโปรแกรมของเราลมเหลวในการ
ทํางาน เชน ขอมูลถูกหารดวยศูนย เขาหาขอมูลใน array ดวยการใช index ที่ไมมีอยูจริง หรือ
อางถึงหนวยความจําที่เปน null เปนตน

ถึงแมวาเรามีวิธีการตรวจสอบ error ตาง ๆ เหลานี้ดว ยการใช if-else หรือ การตรวจสอบอื่น ๆ ที่


ทําใหโปรแกรมของเราทํางานไดราบรื่น แตจะทําให code ของเราดูแลววุนวายเพราะถามีการ
ตรวจสอบ error มาก code ของเราก็จะดูซับซอนมากยิ่งขึ้น แตไมไดหมายความวาเราจะไม
ตรวจสอบ และดักจับ error เหลานี้ การนําเอา exception เขามาเปนตัวชวยในการตรวจสอบ
และดักจับ ทําใหเกิดการแยกสวนของ code ที่ทํางานไดราบรื่น ออกจากสวนของ code ที่
จัดการเกี่ยวกับ error ทําใหเราสามารถที่จะคนหาสวนของ code ทั้งสองไดงายขึ้น ถามีการ
เปลี่ยนแปลง code ในอนาคต ขอดีอีกอันหนึ่งของ exception ก็คือ ทําใหการตรวจสอบและดัก
จับเปนไปอยางเฉพาะเจาะจง ตรงกับ error ที่เกิดขึ้น ทําใหการแกไขเปนไปอยางถูกตอง และ
เนื่องจากวา error มีหลากหลายรูปแบบ เราตองเขียน code ขึ้นมารองรับไมเชนนั้นแลว
โปรแกรมของเราก็จะไมผานการ compile

เราไมจําเปนที่จะตองใช exception ในการตรวจสอบและดักจับ error ในโปรแกรมเสมอไป


เพราะการใช exception จะเสียคาใชจายในการประมวลผลมาก ทําใหโปรแกรมทํางานชาลง
ดังนั้นเราจะตองตัดสินใจใหดีวาควรจะใช exception ในกรณีไหน อยางไร ตัวอยางของ
โปรแกรมที่ไมตองใช exception ก็นาจะเปนการใสขอมูลนําเขาแบบผิด ๆ ของ user ซึ่งถือเปน
เรื่องปกติ ถาเรามัวแตเสียเวลาในการดักจับดวยการใช exception แทนการตรวจสอบและดักจับ
ทั่วไปโปรแกรมของเราก็จะเสียเวลาในการประมวลผลสวนอื่น ๆ ไป

การใช exception ใน Java นั้นจะโดยทั่วไปจะเปนการโยน (throw) การจัดการ error ที่เกิดขึ้น


ใหกับ สวนของ code ที่ทําหนาที่ในดานการจัดการกับ error นั้น ๆ ที่เกิดขึ้น ซึ่ง code สวนนี้จะ
คอยดักจับ (catch) ถามีการโยนเกิดขึ้น แตไมจําเปนที่ code สวนนี้จะตองแกไข error ที่เกิดขึ้น
อาจเปนเพียงการดักจับเทานั้น เพื่อใหโปรแกรมทํางานตอไปไดเทานั้น

เราคงไมพูดถึงขอมูลตาง ๆ ที่เกี่ยวกับ error และ exception มากนักแตจะเนนการใช


exception ในการตรวจสอบและดักจับ error แบบพอประมาณ ผูที่สนใจก็สามารถที่จะหาอานได
จากหนังสือ Java ทั่ว ๆ ไป หรือใน web sit ของ Java เอง
เริ่มตนการเขียนโปรแกรมดวย Java

Exception โดยปกติจะเปน object จาก sub class (class ใด class หนึ่ง) ของ class
Throwable ที่ Java มีให และ class หลัก ๆ สอง class ที่เกิดจาก class Throwable โดยตรงที่

intro. to Java (FEU.faa)


เปน class หลักของ class ที่เปน class เฉพาะของการจัดการกับ error คือ class Error และ
class Exception (ภาพที่ 7 - 1)

Throwable

Error Exception

Other sub classes Other sub classes

ภาพที่ 7-1 Class Throwable และ sub classes

โดยการออกแบบของ Java เราไมควรที่จะ catch error ที่เกิดขึ้นจากการตรวจสอบของ class


Error เพราะ error ที่เกิดขึ้นจะเปน error ที่ โดยทั่วไปจะไมเกิดขึ้น (หรือไมคาดวาจะเกิด) ซึ่งมี
อยูสาม class คือ ThreadDeath LinkageError และ VirtualMachineError เราคงจะไมเขาไป
วุนวายกับ class ทั้งสามเพราะวา เราไมสามารถที่จะแกไข error ที่เกิดขึ้นนี้ไดโดยตรง และตอง
ใชการตรวจสอบอยางละเอียดกับ error ที่เกิดขึ้น (แตคงตองหวังในใจวา error ที่วาคงไมเกิด
ขึ้นกับเรา)

[การ throw exception]

เราจะมาดูตัวอยางการใช exception ในการจัดการกับ error ที่เกิดขึ้นในโปรแกรมของเรา ดวย


โปรแกรมตัวอยางตอไปนี้

1: /**
2: Throwing exception when divided by zero
3: */
4:
5: import static java.lang.System.out;
6:
7: class ThrowException {
8: public static void main(String[] args) throws Exception {
9: int number = 10;
10: int divider = 0;
11:
12: out.printf("Divide %d by %d%n", number, divider);
13: int result = number / divider;
14: out.printf("Result is %d%n", result);
15: }
16: }

โปรแกรมตัวอยางดานบนนี้ทําการ throw exception ซึ่งเปน exception ที่เกิดขึ้นในขณะที่


โปรแกรมกําลังไดรับการประมวลผล (เชน การหารตัวเลขที่เปน int ดวย 0 – เราตั้งใจให error
นี้เกิดขึ้น) โปรแกรมตัวอยางของเราไมไดทําการดักจับใด ๆ ทําแตเฉพาะการตรวจสอบ error ที่
เกิดขึ้นเทานั้น ดังที่เห็นจากผลลัพธของการ run นี้

Divide 10 by 0
Exception in thread "main" java.lang.ArithmeticException: / by zero
at ThrowException.main(ThrowException.java:13)

232
บทที่ 7: การตรวจสอบและดักจับ Error (Exceptions)

จะเห็นวา Java จะฟองใหเรารูวา error ที่เกิดขึ้นเปน error ชนิดไหน (ArithmeticException) ที่


พยายามหาร int ดวย 0 พรอมกับบอกวาเกิดขึ้นในบรรทัดที่เทาไร หลังจากนั้นก็ยุติการทํางาน

intro. to Java (FEU.faa)


[การ catch exception]

ถาหากวาเราตองการที่จะแกไข หรือทําการใด ๆ สักอยางหนึ่งกับ error ที่เกิดขึ้นเราตองใช try


และ catch ดังตัวอยางตอไปนี้

1: /**
2: Manipulate error with try and catch
3: */
4:
5: import static java.lang.System.out;
6:
7: class TryAndCatchExample1 {
8: public static void main(String[] args) {
9: int number1 = 15;
10: int number2 = 0;
11:
12: try {
13: out.println("Division by zero in a try block.");
14: int result = number1 / number2;
15: //line below won't get executed
16: out.println("Leaving a try block.");
17: }
18: catch(ArithmeticException ex) {
19: out.println("Exception caught in a catch block.");
20: }
21:
22: out.println("After a try block.");
23: }
24: }

โปรแกรม TryAndCatchExample1.java เปนโปรแกรมตัวอยางการใช try และ catch ในการ


จัดการกับการหาร int ดวย 0 (เราจะไมใชการ throws exception เหมือนกับตัวอยางแรก) เรา
กําหนดให number1 มีคาเปน 15 และ number2 มีคาเปน 0 พรอมทั้งกําหนดให

result = number1 / number2;

ใน block ของ try ซึ่งประโยคดังกลาวจะทําใหเกิดการโยน exception ไปยัง block ของ catch


และใน block ของ catch เราจะดักดวย exception ที่ชื่อ ArithmaticException เราไมไดทํา
อะไรมากมายไปกวาการแสดงขอความวามีการดักจับ error ผลลัพธที่ไดจากการ run คือ

Division by zero in a try block.


Exception caught in a catch block.
After a try block.

จากผลลัพธที่ได code ที่ไดรับการประมวลผลคือ ประโยคที่อยูใน block ของ try 2 ประโยคแรก


โดยเฉพาะประโยคที่สอง ที่ทําใหการประมวลกระโดดไปยัง block ของ catch ทําใหประโยคที่
สาม (บรรทัดที่ 16) ไมไดรับการประมวลผล และหลังจากที่ประมวลผลประโยคที่อยูใน block
ของ catch แลว การประมวลผลประโยคสุดทายที่ตามหลัง block ของ catch จึงเกิดขึ้น

เราสามารถใช ตัวแปร ex ที่เราไดประกาศใน parameter list ของ method catch() แสดงถึง


ขอความที่บงบอกเกี่ยวกับ error ที่เกิดขึ้น ดวยการเพิ่มประโยคตอไปนี้ใน block ของ catch

out.println(ex.getMessage());
ex.printStackTrace();

ประโยคแรกเรียกใช method getMessage() ที่เกิดขึ้นทําการแสดงผลไปยังหนาจอ สวน


ประโยคที่สองเปนการตรวจสอบถึงที่มาของ error ที่เกิดขึ้น ดังที่แสดงใหเห็นจากการ run
โปรแกรมอีกครั้งหลังจากการเปลี่ยนแปลง code ใน block ของ catch

Division by zero in a try block.


Exception caught in a catch block.
/ by zero

233
เริ่มตนการเขียนโปรแกรมดวย Java

java.lang.ArithmeticException: / by zero
at TryAndCatchExample1.main(TryAndCatchExample1.java:14)
After a try block.

intro. to Java (FEU.faa)


เราไมจําเปนที่จะตองแสดงผลที่เกิดขึ้นไปยังหนาจอ สวนใหญแลวขอมูลเหลานี้จะเปนประโยชน
ตอการตรวจสอบถึง error ที่เกิดขึ้นเทานั้นเอง สิ่งที่สําคัญที่เราตองกังวลก็คือ การตรวจสอบและ
ดักจับ error ที่อาจเกิดขึ้น

Java กําหนดให try และ catch เปนของคูกัน เราไมสามารถที่จะแยก block สองออกจากกันได


ถาเรากําหนดใหมีประโยคอะไรก็ไดสักประโยคหนึ่งระหวาง block ของ try และ catch โปรแกรม
ของเราจะ compile ไมผาน ดังตัวอยางที่มีการเพิ่มประโยคระหวาง block ทั้งสองนี้



out.println("Leaving a try block.");
}
out.pritnln("In between try and catch blocks.");
catch(ArithmeticException ex) {

ถาเรา compile เราจะได error ดังที่เห็นนี้

TryAndCatchExample1.java:9: 'try' without 'catch' or 'finally'


try {
^
TryAndCatchExample1.java:15: 'catch' without 'try'
catch(ArithmeticException ex) {
^
2 errors

ตัวอยางการใช try และ catch ที่ classic อีกอันหนึ่งก็คือ การใช try และ catch ใน loop ดัง
ตัวอยางตอไปนี้

1: /**
2: Using try and catch in a loop
3: */
4:
5: import static java.lang.System.out;
6:
7: class TryAndCatchExample2 {
8: public static void main(String[] args) {
9: int number = 10;
10:
11: for(int index = 5; index >= -1; index--)
12: try {
13: out.printf("try block: index = %d%n", index);
14: int result = number / index;
15: out.println("Leaving a try block.");
16: }
17: catch(ArithmeticException e) {
18: out.println("Exception caught in catch block.");
19: out.println(e.getMessage());
20: e.printStackTrace();
21: }
22:
23: out.println("After a try and catch blocks.");
24: }
25: }

กอนที่จะอธิบายถึงการทํางานของตัวโปรแกรม เรามาดูผลลัพธที่ไดจากการ run

try block: index = 5


Leaving a try block.
try block: index = 4
Leaving a try block.
try block: index = 3
Leaving a try block.

234
บทที่ 7: การตรวจสอบและดักจับ Error (Exceptions)

try block: index = 2


Leaving a try block.
try block: index = 1

intro. to Java (FEU.faa)


Leaving a try block.
try block: index = 0
Exception caught in a catch block.
/ by zero
java.lang.ArithmeticException: / by zero
at TryAndCatchExample2.main(TryAndCatchExample2.java:14)
try block: index = -1
Leaving a try block.
After a try and catch blocks.

ประโยคใน block ของ try จะถูกประมวลผลจนกวา คาของ index จะเปน 0 จึงจะกระโดดไป


ประมวลผลประโยคที่อยูใน block ของ catch หลังจากนั้นก็กลับไปประมวลผลประโยคที่
เหลืออยูใน block ของ try และประโยคที่อยูทายสุดในโปรแกรม อีกประโยคหนึ่ง

จะเห็นไดวาเรามีทางเลือกอยูสองทางในการจัดการกับ error ทางแรกคือ ยุติการทํางาน


(terminate) ของโปรแกรม หรือ code สวนนั้น ๆ ที่ทําใหเกิด error หรือ ทางที่สอง ซึ่งเปน
ทางเลือกที่ทําใหโปรแกรม หรือ code สวนนั้น ๆ กลับไปรับการประมวลผลใหม (resumption)
ทั้งสองวิธีเปนการตรวจสอบและดักจับ error ที่ดีพอกัน ทั้งนี้ก็ขึ้นอยูกับลักษณะของงานที่ทําอยู
ลองมาดูตวั อยางอีกสักตัวหนึ่งในการใช class NumberFormatException ในการดักจับ error

1: /**
2: Using NumberFormatException
3: */
4:
5: import static java.lang.System.out;
6:
7: class NumFormatException {
8: public static void main(String[] args) {
9: int number, sum = 0;
10:
11: for(int i = 0; i < args.length; i++) {
12: try {
13: number = Integer.parseInt(args[i]);
14: sum += number;
15: }
16: //ignore input that's not a number
17: catch(NumberFormatException e) {
18: e.printStackTrace(System.out);
19: }
20: }
21: out.printf("Sum is %d%n", sum);
22: }
23: }

เรากําหนดใหโปรแกรมของเรารับขอมูลจาก command line argument ซึ่งเปนวิธีการที่ยอมให


ผูใชใสขอมูลตามหลังชื่อของโปรแกรม (ดูจากผลลัพธการ run) เราจะทําการหาผลรวมของ
ตัวเลขเหลานั้นทั้งหมดจนกวาผูใชจะพอใจ คือ กดปุม <enter> หลังจากใสขอความจนพอใจ
ใน catch block เราดักจับดวย NumberFormatException ซึ่งจะไมยอมรับขอมูลนําเขาที่ไมใช
ตัวเลขที่เปน int และทุกครั้งที่เจอขอมูลแบบนี้โปรแกรมจะสงรายละเอียดไปใหผูใช และจะหา
ผลรวมของขอมูลที่ถูกตองนั้น ดังที่แสดงไวดา นลางนี้

D:\>java NumFormatException
1 2 3 r 5 6 t y
java.lang.NumberFormatException: For input string: "r"
at java.lang.NumberFormatException.forInputString(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
at NumFormatException.main(NumFormatException.java:13)
java.lang.NumberFormatException: For input string: "t"
at java.lang.NumberFormatException.forInputString(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
at NumFormatException.main(NumFormatException.java:13)
java.lang.NumberFormatException: For input string: "y"
at java.lang.NumberFormatException.forInputString(Unknown Source)

235
เริ่มตนการเขียนโปรแกรมดวย Java

at java.lang.Integer.parseInt(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
at NumFormatException.main(NumFormatException.java:13)

intro. to Java (FEU.faa)


Sum is 17

ในการสราง block สําหรับการ catch error ที่เกิดขึ้นเราไมจําเปนที่จะตองมี catch block เพียง


block เดียว เราอาจมีมากกวาหนึ่ง catch block ไดแตมีขอแมวา การ catch exception ตองทํา
จาก sub class ออกไปหา super class มิฉะนั้นแลวการดักจับในหลาย ๆ block อาจไมเกิดขึ้น
เชน

try {


}
catch(Exception e) {


}
catch(ArithmeticException ex) {


}

เนื่องจากวา class ArithmeticException เปน class ที่เกิดมาจาก class Exception ดังนัน ้ error
ที่เกิดขึ้นก็จะถูกดักจับใน catch block ทั้งหมด การดักจับใน catch block ที่สองจึงไมเกิดขึ้น

เรามาลองดูตัวอยางการดักจับ error ในรูปแบบนี้กันดู

1: /**
2: Catching multiple errors
3: */
4:
5: import static java.lang.System.out;
6:
7: class MultipleCatches {
8: public static void main(String[] args) {
9: //force divide by zero at index = 2
10: int []number = {1, 2, 0, 4};
11: int num = 12;
12: int count = 0;
13:
14: //force array index out of bound at index = 4
15: for(int i = 0; i < number.length + 1; i++) {
16: try {
17: num /= number[i];
18: out.printf("result = %d%n", num);
19: }
20: catch(ArithmeticException e) {
21: out.println("error message: " + e.getMessage());
22: }
23: catch(ArrayIndexOutOfBoundsException e) {
24: out.println("error message: " + e.getMessage());
25: }
26: finally {
27: out.printf("In finally block #%d%n", count++);
28: }
29: }
30: out.println("After a for loop.");
31: }
32: }

เราตองการที่จะบังคับใหเกิด error สองอยางคือ หารดวยศูนย และเขาหา index ของ array ที่


ไมมีอยูจริง โดยเรากําหนดใหขอมูลที่ index = 2 มีคาเปนศูนย และให for/loop ทํางานเกินไป
หนึ่งครั้ง การ run โปรแกรมตัวอยางของเราทําใหเกิด ผลลัพธดังนี้คือ

result = 12
In finally block #0
result = 6
In finally block #1

236
บทที่ 7: การตรวจสอบและดักจับ Error (Exceptions)

error message: / by zero


In finally block #2
result = 1

intro. to Java (FEU.faa)


In finally block #3
error message: 4
In finally block #4
After a for loop.

จะเห็นวาโปรแกรมของเราดักจับ error ไดตามที่เราตั้งใจไว การหารดวยศูนย และการอางถึง


index ของ array ที่ไมมีอยูจริง ถูกดักไวทั้งสองตัว ถาเปนการจัดการที่เปนการพัฒนาโปรแกรม
จริง ๆ เราคงทําแคการดักจับไมได คงตองมีกระบวนการอื่น ๆ ในการทําใหโปรแกรมทํางานได
อยางราบรื่น และกระบวนการจัดการกับ error ที่เกิดขึ้นก็ตองมีความซับซอนเพิ่มขึ้น

Java ยังมี block อีก block หนึ่งที่เอาไวดักจับ error ที่เกิดขึ้น ที่ซึ่งเปน block สุดทายของการ
catch ทั้งหมด โดยกําหนดให block นี้ทีชื่อวา finally (บรรทัดที่ 26 ของโปรแกรม
MultileCatches.java)

finally เปนการดักจับ error สุดทายของการดักทั้งหมด ดังนั้น finally จะอยูที่อื่นไมไดนอกจาก


block สุดทายของ catch block และ finally block ที่เราใชจะถูกประมวลผลเสมอ ไมวาอะไรจะ
เกิดขึ้นก็ตามใน block อื่น ๆ กอนหนา ดังตัวอยางของผลลัพธที่ไดแสดงใหดู

โดยทั่วไปหนาที่หลักของ finally จะเปนการเก็บกวาด code ที่โปรแกรมตองการประมวลผล


(เชน การปด เปดไฟล)กอนออกจาก ตัว method main() และกลับออกไปสูระบบในที่สุด
โปรแกรมตอไปเปนตัวอยางการใช throws และ try/catch รวมกัน

1: /**
2: Using throw with try/catch block
3: */
4:
5: import java.util.Scanner;
6: import java.io.*;
7: import static java.lang.System.out;
8:
9: class ThrowsWithTry {
10: public static void main(String[] args) throws IOException {
11: Scanner input = new Scanner(System.in);
12: int first, divider;
13:
14: //keep looping until error occur
15: while(true) {
16: out.print("Enter a number: ");
17: //get first number
18: first = input.nextInt();
19: //get second number
20: out.print("Enter a divider: ");
21: divider = input.nextInt();
22: //try dividing first by divider
23: try {
24: double result = first / divider;
25: out.println("Result = " + result);
26: }
27: catch(ArithmeticException e) {
28: out.println("Exception: " + e.getMessage());
29: e.printStackTrace();
30: }
31: }
32: }
33: }

โปรแกรม ThrowsWithTry.java เปนโปรแกรมที่ตองการแสดงการใช throws และ try/catch


ควบคูกัน โดยตัวโปรแกรมเองจะทําการอานขอมูลสองตัวจาก keyboard ซึ่งกําหนดใหขอ
 มูลทั้ง
สองตัวเปน int และจะทําการหาผลลัพธของการหารขอมูลตัวแรก ดวยขอมูลตัวที่สอง

โปรแกรมจะหยุดการทํางานก็ตอเมื่อผูใชใสขอมูลที่ไมใชตัวเลข และจะทําการฟองถาขอมูลตัวที่
สองของการหารมีคาเปนศูนย ผลลัพธที่ไดจากการ run คือ

237
เริ่มตนการเขียนโปรแกรมดวย Java

D:\>java ThrowsWithTry
Enter a number: 23
Enter a divider: 12

intro. to Java (FEU.faa)


Result = 1.0
Enter a number: 45
Enter a divider: 0
Exception: / by zero
java.lang.ArithmeticException: / by zero
at ThrowsWithTry.main(ThrowsWithTry.java:26)
Enter a number: 34
Enter a divider: 65
Result = 0.0
Enter a number: try
Exception in thread "main" java.util.InputMismatchException
at java.util.Scanner.throwFor(Unknown Source)
at java.util.Scanner.next(Unknown Source)
at java.util.Scanner.nextInt(Unknown Source)
at java.util.Scanner.nextInt(Unknown Source)
at ThrowsWithTry.main(ThrowsWithTry.java:19)

จากผลลัพธที่ไดจะเห็นวาโปรแกรมของเราจะฟองถามีการหารดวยศูนย (ทีเ่ กิดจากการดักจับ


ของเราในบรรทัดที่ 27) และหยุดถาขอมูลไมใช int (ในตัวอยางนี้ผูใชใสคําวา "try" ซึ่งเกิดจาก
การ throws ของ Java) โปรแกรมของเราจะทํางานไปเรื่อย ๆ ถาผูใชใสขอมูลที่ถูกตองตามที่
โปรแกรมไดกําหนดไว แตนี่ไมใชการตรวจสอบที่ดีเทาไรนัก ถาเราตองการที่จะบอกผูใชวา
ขอมูลไมถูกตอง และใหใสขอมูลใหมเราก็ตองแกไข code ของเราใหรองรับการทําใหม ซึ่งอาจ
ทําไดดังนี้

1: /**
2: Using throws and try together
3: */
4:
5: import static java.lang.System.out;
6: import java.util.Scanner;
7: import java.util.InputMismatchException;
8:
9: class ThrowsWithTry1 {
10: public static void main(String[] args) {
11: Scanner input = new Scanner(System.in);
12: int first, divider, result;
13: boolean done = false;
14:
15: //continue till acceptable input is entered
16: while(!done) {
17: try {
18: out.print("Enter a number: ");
19: first = input.nextInt();
20: out.print("Enter a divider: ");
21: divider = input.nextInt();
22:
23: result = first / divider;
24: out.printf("Result is %d%n", result);
25: done = true;
26: }
27: catch(InputMismatchException ie) {
28: out.printf("Exception: %s%n", ie);
29: //discard this input
30: input.nextLine();
31: out.println("Try again.");
32: }
33: catch(ArithmeticException ar) {
34: out.printf("Exception: %s%n", ar);
35: out.println("Zero not accepted.");
36: }
37: }
38: }
39: }

ผลลัพธที่เราไดคือ

D:\>java ThrowsWithTry1
Enter a number: 3

238
บทที่ 7: การตรวจสอบและดักจับ Error (Exceptions)

Enter a divider: e
Exception: java.util.InputMismatchException
Try again.

intro. to Java (FEU.faa)


Enter a number: 3
Enter a divider: 0
Exception: java.lang.ArithmeticException: / by zero
Zero not accepted.
Enter a number: 3
Enter a divider: 4
Result is 0

เราดัดแปลงโปรแกรมของเราใหมีการตรวจสอบขอผิดพลาดสองสวนคือ 1) user ใสขอมูลที่


ไมใชตัวเลข (บรรทัดที่ 27) และ 2) user ใสตัวหารที่เปนศูนย (บรรทัดที่ 33) โปรแกรมของเรา
จะบังคับให user ใสขอมูลจนกวาขอมูลที่ใสเขามาเปนขอมูลที่ถูกตอง ถาหากเปนขอมูลที่ไมอยู
ในเงื่อนไขที่เรากําหนดไว while/loop ของเราก็จะทํางานไปเรื่อย ๆ (ตัวแปร done เปนตัว
กําหนดการยุตก ิ ารทํางาน) ผูอานจะเห็นไดจากผลลัพธที่เราไดวาขอมูลทีไ่ มถูกตองจะไมมีการ
ประมวลผลจากโปรแกรม แตโปรแกรมจะสงขอความบอกไปยังผูใชใหทราบวา ขอมูลไมถก ู ตอง
พรอมทั้งบังคับใหผูใชใสขอมูลใหมจนกวาจะถูกตอง

7.2 การสราง exception ขึ้นมาใชเอง

เราสามารถที่จะเขียน exception ขึ้นมาใชเองไดถาหากเราเห็นวาการสงขอความบอกไปยัง


user เกี่ยวกับ error ที่เกิดขึ้นไมชัดเจน หรือละเอียดพอ โปรแกรม TestInputValidation.java
เปนโปรแกรมที่เราไดดัดแปลงมาจากโปรแกรม ThrowsWithTry1.java โดยกําหนดใหมีการดัก
จับขอมูลที่มีคาเปนศูนยจาก exception ZeroDivideException ที่เราเขียนขึ้นเอง

1: /**
2: Using user's exception
3: */
4:
5: import java.util.Scanner;
6: import java.util.InputMismatchException;
7: import static java.lang.System.out;
8:
9: class TestInputValidation {
10: public static void main(String[] args) {
11: Scanner input = new Scanner(System.in);
12: int first, divider, result;
13: boolean done = false;
14:
15: //continue till acceptable input is entered
16: while(!done) {
17: try {
18: out.print("Enter a number: ");
19: first = input.nextInt();
20: out.print("Enter a divider: ");
21: divider = input.nextInt();
22:
23: result = divide(first, divider);
24: out.printf("Result is %d%n", result);
25: done = true;
26: }
27: catch(InputMismatchException ie) {
28: out.printf("Exception: %s%n", ie);
29: //discard this input
30: input.nextLine();
31: out.println("Try again.");
32: }
33: catch(ZeroDivideException ar) {
34: out.printf("Exception: %s%n", ar);
35: out.println("Zero not accepted.");
36: }
37: }
38: }
39:
40: //division of two numbers
41: //if n is 0, ZeroDivideException is thrown
42: public static int divide(int m, int n) throws ZeroDivideException {
43: int result;

239
เริ่มตนการเขียนโปรแกรมดวย Java

44: try {
45: result = m / n;
46: }

intro. to Java (FEU.faa)


47: catch(ArithmeticException ar) {
48: throw new ZeroDivideException();
49: }
50: return result;
51: }
52: }

เราไดเปลี่ยนโปรแกรมกอนหนานี้มากพอสมควรในการที่จะอานขอมูลใหถูกตอง พรอมทั้งแสดง
ถึงการเขียน exception ขึ้นมาใชเอง เราเริ่มดวยการสราง method readInt() ขึ้นมาใหม โดย
กําหนดใหผูใชตองใสขอมูลใหถูกตอง ถาไมถูกเราก็จะวนกลับไปถามใหมจนกวาจะได

Method ตัวที่สองที่เราสรางขึ้นมาคือ method divide() ที่รับ parameter 2 ตัว ทําหนาทีใ่ นการ


หารตัวเลขตัวแรกดวยตัวเลขตัวที่สอง ภายใน method divide() นี้เราจะดักจับ error ที่เกิดขึ้น
ผานทาง ArithmeticException ดวย การ throw exception ที่เราไดสรางขึ้นเอง ดวยคําสัง่

catch(ArithmeticException e) {
throw new ZeroDivideException(); //throw new exception
}

แตกอนที่เราจะเรียกใช exception ที่วานี้เราตองสราง code สําหรับ exception เสียกอน ซึ่งเรา


ไดออกแบบใหเปนการดักจับแบบงาย ๆ ดวยการสราง class ZeroDivideException จาก class
Exception เพื่อที่จะใหเราสามารถที่จะไดรับการถายทอดคุณสมบัติจาก class Exception เรา
กําหนดให code ของ class ZeroDivideException มีดังนี้

1: /**
2: Exception for division by zero
3: */
4:
5: class ZeroDivideException extends Exception {
6: //default constructor
7: ZeroDivideException() {
8: super("divide by zero"); //call super class' constructor
9: }
10:
11: ZeroDivideException(String message) {
12: super(message);
13: }
14: }

เราไมไดทําอะไรมากไปกวาเรียกใช constructor ของ class Exception ดวยขอความที่เรา


ตองการใหปรากฏหาก error ที่วานี้เกิดขึ้น (divide by zero) และสวนที่สําคัญของการดักจับ
error คือ catch ตัวที่สองที่อยูในสวนของ code นี้


catch(InputMismatchException ie) {
out.printf("Exception: %s%n", ie);
//discard this input
input.nextLine();
out.println("Try again.");
}
catch(ZeroDivideException ar) {
out.printf("Exception: %s%n", ar);
out.println("Zero not accepted.");
}

เราเรียก method divide() ภายใน try และถาหากการหารที่เกิดขึ้นมี error (หารดวยศูนย) มันก็


จะถูกจับใน catch block ที่เราไดเรียกดวย object ที่มาจาก class ZeroDivideException ที่เรา
ไดสรางขึ้น หลังจากที่ลอง run ดูเราไดผลลัพธดังนี้

D:\>java TestInputValidation

Enter a number: 23
Enter a divider: 0
Exception: ZeroDivideException: divide by zero

240
บทที่ 7: การตรวจสอบและดักจับ Error (Exceptions)

Zero not accepted.


Enter a number: 3
Enter a divider: t

intro. to Java (FEU.faa)


Exception: java.util.InputMismatchException
Try again.
Enter a number: 4
Enter a divider: 3
Result is 1

ผลลัพธที่ไดก็เปนไปตามที่เราไดคาดไว คือ error ถูกดักจับไวไดทั้งสองตัวคือ ขอมูลที่ไมใช


ตัวเลข และขอมูลที่เปนศูนย

ตัวอยางตอไปนี้เปนการกําหนดให user ใสขอมูลในกลุม (range) ที่เราไดกําหนดไว ซึ่งถาไม


เปนไปตามนี้เราก็จะฟองไปยังผูใชทันที เหมือนดังเชนที่เราไดทํามากอนหนานี้

1: /**
2: Catching error within a given range
3: */
4:
5: import java.util.Scanner;
6: import static java.lang.System.out;
7:
8: class CheckRange {
9: public static void main(String[] args) {
10: Scanner input = new Scanner(System.in);
11:
12: try {
13: out.print("Enter start number: ");
14: int first = input.nextInt();
15: out.print("Enter end number: ");
16: int second = input.nextInt();
17: out.print("Enter number to check: ");
18: int third = input.nextInt();
19:
20: //check if the third is between first and second
21: checked(first, second, third);
22: out.printf("Number %d is in [%d, %d]%n", third, first, second);
23: }
24: catch(OutOfRangeException e) {
25: e.printStackTrace(System.err);
26: }
27: }
28:
29: //method to validate range
30: public static void checked(int start, int stop, int num)
throws OutOfRangeException {
31: if(num < start) {
32: String errMsg = new String(num + " < " + start);
33: throw new OutOfRangeException(errMsg, start, stop);
34: }
35: if(num > stop) {
36: String errMsg = new String(num + " > " + stop);
37: throw new OutOfRangeException(errMsg, start, stop);
38: }
39: }
40: }

โปรแกรมตัวอยางนี้รับขอมูล ที่เปนตัวเลขสามตัว โดยจะทําการตรวจสอบวาตัวเลขตัวที่สามอยู


ระหวางตัวแรกและตัวที่สองหรือไม ถาไมก็จะ throw exception OutOfRangeException ที่ได
ออกแบบไวดังนี้

1: /**
2: OutOfRangeException.java
3: */
4:
5: public class OutOfRangeException extends Exception {
6: //default constructor
7: OutOfRangeException() {}
8:
9: //constructor

241
เริ่มตนการเขียนโปรแกรมดวย Java

10: OutOfRangeException(String message, int p, int q) {


11: super(message);
12: }

intro. to Java (FEU.faa)


13: }

จะเห็นวาโดยสวนใหญแลวเราจะเรียกใช super class constructor ทํางานใหเรา มากกวาที่จะ


เขียน code ขึ้นมา ซึ่งเปนสิ่งที่ทําใหการออกแบบของเราไวขึ้น เราเพียงแตเขียนถึงสิ่งที่บงบอก
ถึง error ทีเกิดขึ้นวาเปนอะไรเทานั้นเอง จากการ run ผลลัพธที่เราไดถาขอมูลไมถูกตองคือ

D:\ >java CheckRange


Enter start number: 34
Enter end number: 56
Enter number to check: 98
OutOfRangeException: 98 > 56
at CheckRange.checked(CheckRange.java:37)
at CheckRange.main(CheckRange.java:21)

7.3 การใช Assertions

โปรแกรมตรวจสอบขอมูล (CheckRange.java) ที่เราไดเขียนขึ้นนั้นตองมีการเรียกใช หรือ


กําหนดใหมีการ throw exception ขึ้นแต Java ยังมีวิธีการอื่นที่เราสามารถนํามาใชในการ
ตรวจสอบ หรือดักจับ error ที่อาจมีในโปรแกรมของเราได สิ่งที่วานั้นคือ Assertion

Assertion เปนการตรวจสอบถึงขอมูล หรือกระบวนการที่อาจไมถูกตองในการประมวลผล (logic


error) ซึ่ง Java มีชนิดของ assertion อยูสองแบบคือ 1). Precondition assertion ซึ่งหมายถึง
สถานะของโปรแกรมในขณะที่ method ถูกเรียกใช และ 2). Postcondition assertion ซึ่ง
หมายถึงสถานะของโปรแกรมหลังจากที่ method ทํางานเสร็จสิ้นแลว ทั้งสองชนิดมีรูปแบบอยู
สองแบบ คือ

1. assert expression; ซึ่งจะ throws AssertionError ถา expression มีคาเปนเท็จ


2. assert expression1 : expression2; ซึ่งจะ throws AssertionError ดวยขอความของ
expression2 ถา expression1 มีคาเปนเท็จ

เราจะทดลองใช assertion กับโปรแกรม CheckRange.java ดวยการเปลี่ยนบางสวนของ code


ดังนี้

1: /**
2: Catching error within a given range
3: */
4:
5: import java.util.Scanner;
6: import static java.lang.System.out;
7:
8: class AssertionTest {
9: public static void main(String[] args) {
10: Scanner input = new Scanner(System.in);
11:
12: out.print("Enter start number: ");
13: int first = input.nextInt();
14: out.print("Enter end number: ");
15: int second = input.nextInt();
16: out.print("Enter number to check: ");
17: int third = input.nextInt();
18:
19: //check if the third input is between first and second
20: assert(third >= first && third <= second) :
message(first, second, third);
21: out.printf("Number %d is in [%d, %d]%n", third, first, second);
22: }
23:
24: //check if p is in [m, n]
25: public static String message(int m, int n, int p) {
26: return String.format("%d is not in [%d, %d]%n", p, m, n);
27: }
28: }

242
บทที่ 7: การตรวจสอบและดักจับ Error (Exceptions)

เราเรียกใช assert() ในบรรทัดที่ 20 ดวยการตรวจสอบคาของตัวแปร third วาอยูระหวาง first


และ second หรือไมซึ่งถาไมขอความที่เราไดกําหนดในบรรทัดที่ 26 จะถูกสงกลับมาจาก

intro. to Java (FEU.faa)


method message() ดังตัวอยางการ run นี้

D:\>java -ea AssertionTest


Enter start number: 2
Enter end number: 6
Enter number to check: 4
Number 4 is in [2, 6]

D:\>java -ea AssertionTest


Enter start number: 2
Enter end number: 4
Enter number to check: 9
Exception in thread "main" java.lang.AssertionError: 9 is not in [2, 4]

at AssertionTest.main(AssertionTest.java:20)

โดยปกติแลวการใช assert() จะใชในการ debug1 โปรแกรมเพื่อตรวจสอบถึง error ที่อาจ


เกิดขึ้น ดังนั้นการ run โปรแกรมจึงตองใชขอกําหนด –ea เปนตัวบอกให Java ทราบถึงการ
เรียกใชดังกลาว ถาเราไมใช –ea ในการ run โปรแกรมเราก็จะไมเห็นขอความใด ๆ ที่เกี่ยวของ
กับ error ที่เกิดขึ้น

สรุป

ในการเขียนโปรแกรมที่ดีนั้น ผูเขียนจะตองคํานึงถึง error ที่อาจเกิดขึ้นในโปรแกรม ซึ่งไดแบง


ออกเปนสองลักษณะใหญ คือ error ที่โปรแกรมเมอรไมมีทางแกไขได คือ อยูนอกเหนือการ
ควบคุมของผูพฒ ั นาโปรแกรม เชน error ที่เกิดจากหนวยความจําไมพอเพียง สวน error อีก
อันหนึ่งที่ผูเขียนโปรแกรมตองตรวจสอบก็คือ error ที่เกิดตอน run โปรแกรม เชน error จาก
การหารดวยศูนย การเขาหา Index ของ array ที่ไมมีอยูจริง การปอนขอมูลที่ไมถูกกับชนิดที่
ตองการ อยางนี้เปนตน

Exception ที่ Java มีใหมีอยูมากพอสมควร และมากพอที่จะเรียกใชไดเกือบทุกกรณีของการ


เขียนโปรแกรม แตถาผูเขียนโปรแกรมไดพัฒนาโปรแกรมที่ไมสามารถใช exception ที่ Java มี
ใหก็สามารถที่จะออกแบบ exception ขึ้นมาใชเองได

ในการดัก error นั้นผูเขียนอาจสราง exception ขึ้นมาใชในการดักจับ error เองได ทั้งนี้ก็ขึ้นอยู


กับลักษณะของงานที่ผูเขียนโปรแกรมกําลังพัฒนาอยู โดยสรุปแลวสิ่งที่ตองควรคํานึงในการ
ตรวจจับ error คือ

9 Exception เปนตัวบงชี้ถึง error ที่เกิดในโปรแกรม


9 Exception เปน object ที่เกิดมาจาก class Throwable
9 เราสามารถที่จะเรียกใช exception ที่มาจาก Java โดยตรง หรือ สรางขึ้นมาใชเอง
9 ถาเราตองการที่จะควบคุม exception ใน method เราตองเขียน code ใน try block และไม
จําเปนที่จะตองมี try block เพียงหนึ่ง block เทานั้น อาจมีมากกวาหนึ่ง block ได
9 Code ที่ใชในการควบคุม หรือ แกไข exception จะตองทําใน catch block ที่ตามหลัง try
block ทันทีและในหนึ่ง try block เราสามารถที่จะมี catch block มากกวาหนึ่งตัวได
9 Finally เปน block ทายสุดที่อยูใน try โดยทัว่ ไปจะใช finally block ในการเก็บกวาดการ
ทํางานของโปรแกรม เชน การปดไฟล
9 การเขียน exception ขึ้นมาใชเอง
9 การเรียกใช assert() สําหรับการ debug โปรแกรม

แบบฝกหัด

1. จงเขียนโปรแกรมที่รับขอมูลจาก keyboard เฉพาะที่เปน int เทานั้นใหใช exception ใน


การดักจับ error ที่ผูใชอาจใสผิด เชน ใสตัวอักษร ใหแสดงขอความฟองทันทีที่ผูใชใสผิด

1
Debug เปนการตรวจสอบขอผิดพลาดที่อาจเกิดขึ้นในขณะที่มีการเขียนโปรแกรมเกิดขึ้น ซึ่งเปนการตรวจสอบถึงความ
ถูกตองของโปรแกรมกอนที่จะเสร็จสิ้น ซึ่งตางจากการสงขอความเตือน user (error message) ถึงขั้นตอนหรือขอมูลที่
ไมตรงกับสิ่งที่โปรแกรมตองการ

243
เริ่มตนการเขียนโปรแกรมดวย Java

2. จงปรับปรุงโปรแกรมที่เขียนขึ้นในขอหนึ่ง โดยเพิ่มการตรวจสอบที่ไมรับตัวเลขที่ต่ํากวาศูนย

intro. to Java (FEU.faa)


3. จงเขียนโปรแกรมที่รับขอมูลจาก keyboard เปนตัวเลขสามตัว โดยกําหนดให ตัวแรกเปน
จุดเริ่มตน ตัวที่สองเปน จุดจบ ของคาขององศา Celsius และตัวที่สามเปนตัวกําหนดการ
เพิ่มคา (step) ใหโปรแกรมทําการคํานวณหา องศา Fahrenheit ตามขอมูลที่กําหนดไวใน
สองตัวแรก และใหทําการเพิ่มขึ้นตามคาของขอมูลตัวที่สาม ดังตัวอยางผลลัพธที่แสดงใหดู
นี้

>java Temperature 0 100 10

CELSIUS FAHRENHEIT
0 32.0
10 50.0
20 68.0
30 86.0
40 104.0
50 122.0
60 140.0
70 158.0
80 176.0
90 194.0
100 212.0

ใหทําการตรวจสอบ และดักจับ error ที่อาจเกิดขึ้นจากการใสขอมูลที่ไมถูกตอง เชน คา


ของการเพิ่มเปนตัวเลขที่นอยกวาศูนย (negative number) คาเริ่มตน มากกวาคาสุดทาย
และจํานวนขอมูลที่นําเขามีจํานวนไมครบตามที่กําหนด ตัวอยางของ error ที่เกิดขึ้น

>java Temperature 0 100 -5


3rd arg < 0 NUMBER = -5

>java Temperature 40 20 5
Ordering Err FIRST = 40 SECOND = 20

>java Temperature 10 40
java.lang.ArrayIndexOutOfBoundsException:
at Temperature.main(Temperature.java:41)

4. จงเขียนโปรแกรมที่หาคาของ mn โดยกําหนดใหทั้ง m และ n มาจาก command line


argument list ใหทําการตรวจสอบและดักจับ error ทุก ๆ กรณีที่อาจทําใหโปรแกรมไม
ทํางาน

5. จงปรับปรุง class Account ในบทที่หกใหมีการตรวจสอบและดักจับ error ที่อาจเกิดขึ้นใน


การ ถอนเงิน ฝากเงิน และ คิดดอกเบี้ย ถา method ใด ๆ ที่ทําหนาที่ดังกลาวไมมี ใหเขียน
ขึ้นใหม พรอมทั้งเขียนโปรแกรมทดสอบ

6. จงปรับปรุงโปรแกรมในขอ เจ็ด แปด และ เกา ในบทที่หา ใหมีการตรวจสอบและดักจับ


error พรอมทั้งเขียนโปรแกรมทดสอบ

244

You might also like