ถามตอบปัญหาจาวา

ถาม ตามที่อาจารย์ให้ทำ assignment โดยให้มีการรับข้อมูลเข้าทางคีย์บอร์ดโดยใช้คลาส Scanner แล้วลองทดสอบการใส่ข้อมูลที่ไม่ใช่ตัวเลขลงไปในขณะที่โปรแกรมต้องการตัวเลขเพื่อให้โปรแกรมมีการจัดการสิ่งผิดปรกติด้วยนั้น ผมเขียนโปรแกรมให้วนลูปเพื่อรับข้อมูลจนกว่าจะไม่ error ผมลองใส่ค่าเป็นตัวอักษรเพื่อดัก error ผลก็คือมันดัก error ได้ แต่ว่าทำไมมันถึงไม่ขึ้นให้กรอกอีกครั้งหนึ่งล่ะครับ เป็นไปได้ไหมครับว่าค่าเดิมมันยังค้างอยู่ (ค่าที่ใส่ให้เกิด error) มันเลยถือว่าค่าที่กรอกนั้นๆ ผิด เลยวิ่งเข้าไปทำใน catch แล้วก็เลยวนอยู่อย่างนี้
ตอบนักศึกษาเข้าใจถูกแล้วครับ scanner ในกรณีที่เราใช้คลาส Scanner เพื่ออ่านข้อมูลที่เป็นตัวเลข มันจะพยายามแปลงตัวอักขระ ให้เป็นค่าตัวเลข แต่เมื่อพบว่าไมอักขระที่ใส่เข้าไปไม่ใช่ตัวเลข มันเกิด InputFormatException เสียก่อนโดยที่ยังไม่ได้สแกนไปจนสุดบรรทัด ซึ่งยังมีตัว <enter> ค้างอยู่ใน buffer เมื่อวนลูปเข้ามา scan
ในรอบถัดไป มันจะเจอ <enter> ตัวเดิม จึงถือว่าผู้ใช้เคาะ <enter> แล้ว มันจึงพยายามแปลงตัวอักขระที่อยู่ใน buffer ให้เป็นตัวเลขอีกครั้ง ก็จะเจอปัญหาเดิมอีกซึ่งถ้าดูในเอกสาร API ของจาวากล่าวไว้ว่า

When a scanner throws an InputMismatchException, the scanner will not pass the token that caused the exception, so that it may be retrieved or skipped via some other method.

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

จะเห็นว่าสิ่งที่นักศึกษาคิดนั้น ถูกต้องแล้ว วิธีแก้ปัญหาคือ ใช้เมธอด nextLine() เพื่ออ่านข้อมูลที่เหลือทิ้งไปทั้งบรรทัด

เพื่อพิสูจน์เรื่องนี้ผมจึงเขียนโปรแกรมสำหรับทดสอบขึ้นมาชื่ว่า TestScan.java ขึ้นมาดังนี้

    
    // Test Exception handling when input non-numeric data with nextInt() method
    import java.util.*;
    
    import java.util.Scanner;
    class TestScan {
    	public static void main(String [] args) {
    		String inputStr="10 233.50 356p tail of string"; 
    		int qty;
    		double price; 
    		String theRest;
    		Scanner sn = new Scanner(inputStr);
    		System.out.println("input string: " + inputStr);
    		qty = sn.nextInt(); // scan for integer
    		System.out.println("Quantity: " + qty);
    		price = sn.nextDouble(); // scan for double
    		System.out.println("Price: " + price);
    		try {
    			int x = sn.nextInt();  // exception occurs here
    		}
    		catch(InputMismatchException e) {
    			String s = sn.next();
    			System.out.println("The token that caused exception was: " + s );
    		}			
    		theRest = sn.nextLine(); // scan for string to the end of line
    		System.out.println("The rest of line: " + theRest);
    	}
    }
    
    ใน catch block ผมใช้เมธอด next() เพื่ออ่านค่า token ที่ทำให้เกิดปัญหาแล้วนำไปพิมพ์เพื่อให้เห็นว่า เจ้าสตริงตัวที่ทำให้เกิดปัญหามีค่าอะไร ผลของการใช้เมธอดนี้ ทำให้ Scanner อ่านค่าที่ทำให้เกิด error ผ่านไป แต่ว่า ยังอาจมี error ที่อื่นอีกก็ได้ เนื่องจากเมธอด next() อ่านไปจนเจอ white space แต่ไม่ได้อ่านไปจนสุดข้อมูลที่อินพุทเข้ามา

    ดังนั้นถ้าข้อมูลที่อินพุทเข้ามา มี white space อยู่หลายที่ Scanner มันจะหยุดสแกนก่อนที่จะไปจบบรรทัด ถ้าต้องการอ่านข้อมูลที่เหลือไปจนสุดบรรทัดให้ใช้เมธอด nextLine() ดังตัวอย่างที่เขียนให้ดู