Exception Handling → try-catch-finally, custom exception

บทนำ: ทำไมต้องจัดการกับ Exception?

เมื่อโปรแกรมทำงาน บ่อยครั้งจะเกิดสิ่งไม่คาดคิด เช่น:

  • ผู้ใช้ input ข้อมูลผิด
  • ไฟล์ที่ต้องการไม่มี
  • Network ขาด
  • หารด้วย 0
  • Array index ออกนอกขอบ

ถ้าไม่จัดการ โปรแกรมจะ “crash” (หยุดทำงาน) อย่างกะทันหัน

Exception Handling คือกลไกที่ให้โปรแกรมของเรา:

  • ตรวจหา ข้อผิดพลาด (detect)
  • จัดการ ข้อผิดพลาด (handle)
  • ทำให้ปลอดภัย (graceful)

Exception คืออะไร?

Exception = สถานการณ์ผิดปกติ ที่เกิดขึ้นระหว่างการทำงาน

textException Hierarchy (ลำดับชั้น):
Throwable
├─ Error (ร้ายแรง - ไม่ควร catch)
│  ├─ OutOfMemoryError
│  └─ StackOverflowError
│
└─ Exception (ปกติ - ควร catch)
   ├─ Checked Exception (ต้อง handle)
   │  ├─ IOException
   │  ├─ FileNotFoundException
   │  └─ ...
   │
   └─ Unchecked Exception (ไม่ต้อง handle แต่ควร)
      ├─ NullPointerException
      ├─ ArrayIndexOutOfBoundsException
      ├─ ArithmeticException
      └─ ...

try-catch: จัดการ Exception

ตัวอย่างที่ 1: Try-Catch พื้นฐาน

javapublic class Main {
    public static void main(String[] args) {
        // ==== ก่อน: ไม่จัดการ ====
        System.out.println("=== ไม่มี try-catch ===");
        int[] numbers = {10, 20, 30};
        System.out.println("Index 0: " + numbers[0]);
        System.out.println("Index 5: " + numbers[5]);  // ❌ Exception!
        System.out.println("โปรแกรมจะหยุดที่นี่");
        
        System.out.println("\n=== มี try-catch ===");
        // ==== หลัง: มี try-catch ====
        try {
            int[] numbers2 = {10, 20, 30};
            System.out.println("Index 0: " + numbers2[0]);
            System.out.println("Index 5: " + numbers2[5]);  // Exception!
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("❌ ข้อผิดพลาด: " + e.getMessage());
        }
        
        System.out.println("✓ โปรแกรมยังทำงานต่อได้");
    }
}

Output:

text=== ไม่มี try-catch ===
Index 0: 10
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 3
	at Main.main(Main.java:...)

=== มี try-catch ===
Index 0: 10
❌ ข้อผิดพลาด: Index 5 out of bounds for length 3
✓ โปรแกรมยังทำงานต่อได้

คำอธิบาย:

  • try { ... } = ส่วนที่อาจเกิด exception
  • catch (ExceptionType e) { ... } = ถ้าเกิด exception ประเภทนั้น ให้ทำนี้
  • ถ้ามี try-catch โปรแกรมจะ continue ไม่หยุด

ตัวอย่างที่ 2: Multiple Catch Blocks

javapublic class Main {
    public static void main(String[] args) {
        // ==== Input จากผู้ใช้ ====
        try {
            String input = "25a";  // ข้อมูลผิด
            int age = Integer.parseInt(input);  // ← อาจ throw NumberFormatException
            System.out.println("อายุ: " + age);
            
            // สมมติว่า input ถูก แล้วหาร
            int divided = 100 / (age - 25);  // ← อาจ throw ArithmeticException
            System.out.println("ผลลัพธ์: " + divided);
        } 
        catch (NumberFormatException e) {
            System.out.println("❌ ข้อผิดพลาด: ต้องป้อนตัวเลข");
            System.out.println("   รายละเอียด: " + e.getMessage());
        }
        catch (ArithmeticException e) {
            System.out.println("❌ ข้อผิดพลาด: หารด้วยศูนย์ไม่ได้");
        }
        catch (Exception e) {
            System.out.println("❌ ข้อผิดพลาดอื่น: " + e.getMessage());
        }
        
        System.out.println("✓ โปรแกรมยังทำงานต่อ");
    }
}

Output:

text❌ ข้อผิดพลาด: ต้องป้อนตัวเลข
   รายละเอียด: For input string: "25a"
✓ โปรแกรมยังทำงานต่อ

คำอธิบาย:

  • สามารถมี หลาย catch blocks ได้
  • Java จะตรวจสอบจากบนลงล่าง เจอตัวแรกที่ match ก็ทำ
  • มี catch ทั่วไป Exception ไว้ที่ท้ายเพื่อ “catch all”

finally: ทำเสมอไม่ว่าจะเกิด Exception หรือไม่

ตัวอย่างที่ 3: Try-Catch-Finally

javapublic class Main {
    public static void main(String[] args) {
        // ==== สมมติ: เปิดทรัพยากร (เช่น file) ====
        System.out.println("=== Try-Catch-Finally ===");
        
        try {
            System.out.println("1. เปิด resource");
            
            // สมมติว่า operation ทำงาน
            int result = 10 / 2;  // ✓ ไม่มี exception
            System.out.println("2. ผลลัพธ์: " + result);
            
        } catch (ArithmeticException e) {
            System.out.println("❌ เกิด exception: " + e.getMessage());
        } finally {
            System.out.println("3. ปิด resource (ทำเสมอ)");
        }
        
        System.out.println("\n=== จะเห็นว่า finally ทำเสมอ ===\n");
        
        // ==== ถ้ามี exception ====
        try {
            System.out.println("4. เปิด resource");
            
            int result = 10 / 0;  // ❌ เกิด exception
            System.out.println("5. ผลลัพธ์: " + result);
            
        } catch (ArithmeticException e) {
            System.out.println("❌ เกิด exception: " + e.getMessage());
        } finally {
            System.out.println("6. ปิด resource (ทำเสมอ)");
        }
    }
}

Output:

text=== Try-Catch-Finally ===
1. เปิด resource
2. ผลลัพธ์: 5
3. ปิด resource (ทำเสมอ)

=== จะเห็นว่า finally ทำเสมอ ===

4. เปิด resource
❌ เกิด exception: / by zero
6. ปิด resource (ทำเสมอ)

คำอธิบาย:

  • finally ทำเสมอ ไม่ว่าจะเกิด exception หรือไม่
  • ใช้เพื่อ cleanup (เปิด file → ต้องปิด file)
  • ใช้เพื่อ ยุติ resources (connection, stream ฯลฯ)

Custom Exception: สร้าง Exception ของตัวเอง

ตัวอย่างที่ 4: สร้าง Custom Exception

บางครั้ง คุณต้อง exception ของตัวเอง เพื่อจัดการสถานการณ์ business logic

java// ==== Custom Exception ====
public class InvalidAgeException extends Exception {
    public InvalidAgeException(String message) {
        super(message);
    }
}

public class InsufficientBalanceException extends Exception {
    private double shortage;
    
    public InsufficientBalanceException(String message, double shortage) {
        super(message);
        this.shortage = shortage;
    }
    
    public double getShortage() {
        return shortage;
    }
}

// ==== Class ที่ใช้ custom exception ====
public class Person {
    private String name;
    private int age;
    
    public Person(String name, int age) throws InvalidAgeException {
        this.name = name;
        
        if (age < 0 || age > 150) {
            throw new InvalidAgeException("อายุต้องอยู่ระหว่าง 0-150 ปี แต่ได้: " + age);
        }
        
        this.age = age;
    }
    
    public String getName() { return name; }
    public int getAge() { return age; }
}

// ==== BankAccount ที่ใช้ exception ====
public class BankAccount {
    private String accountNumber;
    private double balance;
    
    public BankAccount(String accountNumber, double initialBalance) {
        this.accountNumber = accountNumber;
        this.balance = initialBalance;
    }
    
    public void withdraw(double amount) throws InsufficientBalanceException {
        if (amount > balance) {
            double shortage = amount - balance;
            throw new InsufficientBalanceException(
                "เงินไม่พอ ต้องเพิ่มเติม " + shortage + " บาท",
                shortage
            );
        }
        balance -= amount;
        System.out.println("✓ ถอนเงิน " + amount + " บาท สำเร็จ");
    }
    
    public double getBalance() {
        return balance;
    }
}

// ==== การใช้งาน ====
public class Main {
    public static void main(String[] args) {
        // ==== Test InvalidAgeException ====
        System.out.println("=== Test InvalidAgeException ===");
        try {
            Person person = new Person("สมชาย", 200);  // ❌ อายุไม่สมเหตุสมผล
        } catch (InvalidAgeException e) {
            System.out.println("❌ " + e.getMessage());
        }
        
        try {
            Person person = new Person("สมชาย", 25);  // ✓ อายุสมเหตุสมผล
            System.out.println("✓ สร้าง Person สำเร็จ: " + person.getName() + " อายุ " + person.getAge());
        } catch (InvalidAgeException e) {
            System.out.println("❌ " + e.getMessage());
        }
        
        // ==== Test InsufficientBalanceException ====
        System.out.println("\n=== Test InsufficientBalanceException ===");
        BankAccount account = new BankAccount("1234", 1000);
        System.out.println("ยอดคงเหลือ: " + account.getBalance() + " บาท");
        
        try {
            account.withdraw(500);  // ✓ เพียงพอ
            System.out.println("ยอดคงเหลือ: " + account.getBalance() + " บาท");
        } catch (InsufficientBalanceException e) {
            System.out.println("❌ " + e.getMessage());
        }
        
        try {
            account.withdraw(700);  // ❌ ไม่เพียงพอ
        } catch (InsufficientBalanceException e) {
            System.out.println("❌ " + e.getMessage());
            System.out.println("   ขาดอยู่ " + e.getShortage() + " บาท");
        }
    }
}

Output:

text=== Test InvalidAgeException ===
❌ อายุต้องอยู่ระหว่าง 0-150 ปี แต่ได้: 200
✓ สร้าง Person สำเร็จ: สมชาย อายุ 25

=== Test InsufficientBalanceException ===
ยอดคงเหลือ: 1000.0 บาท
✓ ถอนเงิน 500.0 บาท สำเร็จ
ยอดคงเหลือ: 500.0 บาท
❌ เงินไม่พอ ต้องเพิ่มเติม 200.0 บาท
   ขาดอยู่ 200.0 บาท

คำอธิบาย:

  • class ... extends Exception = สร้าง exception ของตัวเอง
  • throw new ...Exception(...) = โยน exception เมื่อมีปัญหา
  • throws InvalidAgeException ในชื่อ method = บอก caller ว่า method นี้อาจโยน exception นี้
  • Custom exception ทำให้ code ชัดเจนว่ากำลังจัดการอะไร

ตัวอย่างที่ 5: Try-Catch-Finally กับ Resource

javaimport java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        // ==== Try-catch-finally เพื่อ cleanup ====
        Scanner scanner = null;
        try {
            scanner = new Scanner("สมชาย\nสมหญิง\nสมศรี");
            
            int count = 0;
            while (scanner.hasNextLine() && count < 2) {
                String name = scanner.nextLine();
                System.out.println("ชื่อ: " + name);
                count++;
            }
            
            // สมมติว่า operation ลบตัวเลข
            int age = Integer.parseInt("25");  // ✓ ถูก
            System.out.println("อายุ: " + age);
            
        } catch (NumberFormatException e) {
            System.out.println("❌ ข้อผิดพลาด: " + e.getMessage());
        } finally {
            if (scanner != null) {
                scanner.close();  // ← ปิด resource เสมอ
                System.out.println("✓ ปิด scanner");
            }
        }
    }
}

Output:

textชื่อ: สมชาย
ชื่อ: สมหญิง
อายุ: 25
✓ ปิด scanner

Try-With-Resources (Java 7+)

ตัวอย่างที่ 6: Auto-Close Resources

Java 7 มีวิธีที่สะดวกกว่า – resources ปิดโดยอัตโนมัติ

javaimport java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        // ==== Try-with-resources (auto-close) ====
        try (Scanner scanner = new Scanner("สมชาย\nสมหญิง")) {
            while (scanner.hasNextLine()) {
                String line = scanner.nextLine();
                System.out.println("- " + line);
            }
        } catch (Exception e) {
            System.out.println("❌ " + e.getMessage());
        }
        // scanner auto-close ที่นี่ (ไม่ต้องใช้ finally)
        
        System.out.println("✓ เสร็จ");
    }
}

Output:

text- สมชาย
- สมหญิง
✓ เสร็จ

ข้อดี:

  • ไม่ต้องเขียน finally เพื่อปิด resource
  • Resource ปิดโดยอัตโนมัติ
  • Code สะอาดกว่า

ตัวอย่างรวม: Application ที่มี Exception Handling

javaimport java.util.Scanner;

// ==== Custom Exception ====
public class InvalidProductException extends Exception {
    public InvalidProductException(String message) {
        super(message);
    }
}

// ==== Product Class ====
public class Product {
    private String id;
    private String name;
    private double price;
    
    public Product(String id, String name, double price) throws InvalidProductException {
        if (id == null || id.isEmpty()) {
            throw new InvalidProductException("ID ของสินค้าต้องไม่ว่าง");
        }
        if (price < 0) {
            throw new InvalidProductException("ราคาต้องไม่น้อยกว่า 0");
        }
        
        this.id = id;
        this.name = name;
        this.price = price;
    }
    
    public String getId() { return id; }
    public String getName() { return name; }
    public double getPrice() { return price; }
    
    @Override
    public String toString() {
        return id + " - " + name + " (" + price + " บาท)";
    }
}

// ==== Inventory ====
public class Inventory {
    private java.util.Map<String, Product> products = new java.util.HashMap<>();
    
    public void addProduct(Product product) throws InvalidProductException {
        if (products.containsKey(product.getId())) {
            throw new InvalidProductException("สินค้า ID " + product.getId() + " มีอยู่แล้ว");
        }
        products.put(product.getId(), product);
    }
    
    public Product getProduct(String id) throws Exception {
        if (!products.containsKey(id)) {
            throw new Exception("ไม่พบสินค้า ID: " + id);
        }
        return products.get(id);
    }
    
    public void displayAll() {
        System.out.println("=== สินค้าทั้งหมด ===");
        for (Product p : products.values()) {
            System.out.println("- " + p);
        }
    }
}

// ==== Main ====
public class Main {
    public static void main(String[] args) {
        Inventory inventory = new Inventory();
        
        // ==== เพิ่มสินค้า ====
        System.out.println("=== เพิ่มสินค้า ===");
        try {
            inventory.addProduct(new Product("P001", "Notebook", 50));
            inventory.addProduct(new Product("P002", "Pen", 10));
            inventory.addProduct(new Product("P003", "Pencil", 5));
            System.out.println("✓ เพิ่มสินค้าสำเร็จ");
        } catch (InvalidProductException e) {
            System.out.println("❌ " + e.getMessage());
        }
        
        // ==== ลองเพิ่มสินค้าผิด ====
        System.out.println("\n=== ลองเพิ่มสินค้าผิด ===");
        try {
            inventory.addProduct(new Product("P001", "Duplicate", 100));
        } catch (InvalidProductException e) {
            System.out.println("❌ " + e.getMessage());
        }
        
        try {
            inventory.addProduct(new Product("P004", "Invalid Price", -10));
        } catch (InvalidProductException e) {
            System.out.println("❌ " + e.getMessage());
        }
        
        // ==== ค้นหาสินค้า ====
        System.out.println("\n=== ค้นหาสินค้า ===");
        try {
            Product p = inventory.getProduct("P001");
            System.out.println("✓ พบ: " + p);
        } catch (Exception e) {
            System.out.println("❌ " + e.getMessage());
        }
        
        try {
            Product p = inventory.getProduct("P999");
        } catch (Exception e) {
            System.out.println("❌ " + e.getMessage());
        }
        
        // ==== แสดงทั้งหมด ====
        inventory.displayAll();
    }
}

Output:

text=== เพิ่มสินค้า ===
✓ เพิ่มสินค้าสำเร็จ

=== ลองเพิ่มสินค้าผิด ===
❌ สินค้า ID P001 มีอยู่แล้ว
❌ ราคาต้องไม่น้อยกว่า 0

=== ค้นหาสินค้า ===
✓ พบ: P001 - Notebook (50.0 บาท)
❌ ไม่พบสินค้า ID: P999

=== สินค้าทั้งหมด ===
- P001 - Notebook (50.0 บาท)
- P002 - Pen (10.0 บาท)
- P003 - Pencil (5.0 บาท)

Best Practices: Exception Handling

ทำได้ไม่ทำ
✓ Catch specific exceptions❌ Catch generic Exception ไม่ระบุเหตุ
✓ ใช้ finally สำหรับ cleanup❌ ลืม cleanup resources
✓ สร้าง custom exception สำหรับ logic❌ ใช้ generic exception ทั้งหมด
✓ Log error message ชัดเจน❌ Silently ignore exception
✓ ใช้ try-with-resources❌ Manual close ใน finally
✓ Re-throw exception เมื่อต้อง❌ Swallow exception โดยไม่จำเป็น

สรุป

Exception Handling คือทักษะที่จำเป็นในการเขียนโปรแกรม ที่มีความเสถียรและปลอดภัย:

  • try-catch – จัดการ exception ที่อาจเกิดขึ้น
  • finally – ให้ความมั่นใจว่า cleanup ทำเสมอ
  • Custom Exception – ทำให้ code ชัดเจนเกี่ยวกับ business logic
  • try-with-resources – วิธีสมัยใหม่ให้ resources ปิดอัตโนมัติ

เมื่อใช้ exception handling อย่างถูกต้อง โปรแกรมจะ:

  • ไม่ crash อย่างกะทันหัน – มี opportunity ในการทำปกติสมควร
  • ชัดเจนกว่า – ผู้อ่าน code รู้ว่า method นี้อาจโยน exception อะไร
  • ปลอดภัยกว่า – resources ถูก cleanup อย่างทั่วถึง
  • ยืดหยุ่นกว่า – สามารถจัดการสถานการณ์ต่างๆ ได้

Exception handling ไม่ใช่เพื่อ “ลับเรื่อง” แต่เพื่อ “จัดการปัญหา ให้เหมาะสม” ทำให้ user experience ดีขึ้น และระบบเสถียรกว่า