Java: 예외 exception

    Java: 예외 exception

    2013, Jul 23    

    Exception이란 런타임에서 발생하는 일종의 의도적으로 만들어낸 에러라고 볼 수 있다. (사용자 정의 에러)

    참고: differences-between-exception-and-error

    API(JDK 혹은 써드파티의 메서드들)에 따라 예외를 내부에서 알아서 처리하거나 사용자에게 미루기도 하는데, API에서 예외를 미루는 경우 사용자 측에서 반드시 예외처리(try-catch 혹은 throws)를 명시해야 한다.

    예외를 미루는 대표적인 API로는:

    • 네트워크 통신
    • 데이터베이스 처리
    • 메모리 & 파일 입출력

    가 있다.

    try-catch

    try {
       비즈니스코드 블록
    } catch(Exception e) {
       예외처리코드 블록, 예외 발생 시에만 실행
    } finally {
       try, catch 뒤에 오며 예외 발생 여부와 관계없이 무조건 실행된다.
    }
    
    try {
        throw new Exception("내가 만든 예외"); //Exception 강제 발생
    } catch (Exception e) {
        //try절에서 던진(발생한) 예외를 처리하는 블록
        System.out.println(e.getMessage()); // 에러 메시지 출력
        e.printStackTrace(); // 에러 발생 시점의 호출 스택 출력
    }
    

    ArrayIndexOutOfBoundsException의 경우 배열의 범위를 초과할 때 발생하는 예외인데 아래처럼 코드작성 시 해당 예외가 발생할 것을 예측하여 작성할 수 있다:

    int[] nums = new int[] {10, 20, 30, 40, 50};
    
    System.out.print("보고싶은 인덱스 : ");
    int index = scan.nextInt();
    
    try {
        System.out.println(nums[index]);
    } catch (ArrayIndexOutOfBoundsException e) {
        System.out.println(e); // 발생한 예외의 메시지를 콘솔에 출력
        System.out.println("올바른 색인번호를 입력하세요.");
    }
    

    모든 경우의 수를 예측하는 것은 불가능하기 때문에 비효율적이긴 해도 다음처럼 모든 예외를 처리하는 방식으로 작성하기도 한다(권장하지 않음):

    try {
        // ...
    } catch (Exception e) {
        // 예외처리 코드
    }
    

    throw / throws

    throw, throws 키워드는 예외가 발생한 지역이 아니라 예외를 발생시킨 메서드를 호출한 지역, 즉 caller에서 예외를 처리하도록 할 때 사용된다. 다음은 m01() 메서드에서 발생한 예외를 caller인 main()에서 처리하는 예다:

    public class Test {
        public static void main(String[] args) {
            Excep ex = new Excep();
            try {
                ex.m01();
            } catch (Exception e) {
                System.out.println("메서드를 호출한 지점");
            }
        }
    }
    
    class Excep {
        public void m01() {
            int[] nums = new int[] { 1 };
            try {
                System.out.println(nums[2]);
                // ArrayIndexOutOfBoundsException 발생
                // new ArrayIndexOutOfBoundsException(); : 1. 예외 객체 생성
                // throws new ArrayIndexOutOfBoundsException : 2. 예외객체를 던진다.
            } catch (Exception e) {
                throw e; // 이 메서드를 호출한 지역이 예외를 받는다
            }
        }
    }
    

    아래처럼 모든 메서드에서 예외를 미루는 방법도 있다:

    class Excep {
        public void m01() throws Exception {
            int[] nums = new int[] {1};
            System.out.println(nums[2]);
        }
    }
    

    finally

    finally는 try절이나 catch절 이후 반드시 실행되는 실행문을 정의한다.

        @Test
        public void finallyTest2() {
            String str = "";
            try {
                str += "try";
                throw new IllegalAccessError();
            } catch (IllegalAccessError e) {
                str += " catch";
            } finally {
                str += " finally";
            }
            Assert.assertEquals("try catch finally", str);
        }
    

    try-catch-finally에서 catch절 없이 try-finally 형태로 작성이 가능하긴 하다. 이 경우 try절에서 예외 발생 시 예외가 미뤄진다:

        public void withOutCatchStatement() {
            try {
                try {
                    @SuppressWarnings("unused")
                    int nan = 1 / 0; // 여기서 발생한 예외는 가장 바깥의 catch에서 받음
                } finally {
                    // catch문이 없으면 finally라도 있어야 컴파일 에러 안남
                    logger.debug("You know nothing John snow!");
                }            
            } catch (ArithmeticException e) {
                logger.error(e.getMessage(), e);
            }
        }
    

    example

    try {
        int[] nums = { 10, 20, 30 };
        System.out.println(nums[5]); // ArrayIndexOutOfBoundsException
    
        Random rnd = null;
        System.out.println(rnd.nextInt()); // NullPointerException
    
    } catch (ArrayIndexOutOfBoundsException e) {
        System.out.println("인덱스범위에러");
    
    } catch (NullPointerException e) {
        System.out.println("null에러");
    }
    // 또 다른 예외 발생?
    catch (Exception e) {
        // 던진 객체를 받을 수 있는 클래스가 없다면 부모 클래스인 Exception에 업캐스팅해서 받는다.
        System.out.println("어쨋든 에러");
    }
    
    public class Ex94 {
        public static void main(String[] args) {
    
            //예외 코드
            // A001 : 배송 중 제품 파손
            // A002 : 배송 중 날짜 지연
            // A003 : 배송 중 잘못 배송
            // B001 : 사용 중 제품 불량
            // B002 : 사용 중 제품 오작동
    
            System.out.println("고객이 제품을 주문..");
    
            System.out.println("1. 주문접수 완료");
            System.out.println("2. 제품 포장");
            System.out.println("3. 제품 발송");
    
            System.out.println("고객이 제품을 수령..");
    
    
            try {
                System.out.println("제품파손 발견"); //A001
    
                throw new MyShopException("A001");
    
            } catch (MyShopException e) {
    
                System.out.println("예외 코드 : " + e);
                System.out.println("내선 번호 : " + e.checkNumber());
            }
        }
    }
    
    //사용자 정의 예외 클래스(Exception 파생 클래스)
    class MyShopException extends Exception {
        //예외 코드 관리-> 담당 상담원 안내
        private String code; //예외코드
    
        MyShopException(String code) {
            this.code = code;
        }
    
        //내선번호
        public String checkNumber() {
            String number = "";
            if (this.code.equals("A001")) number = "100";
            else if (this.code.equals("A002")) number = "110";
            else if (this.code.equals("A003")) number = "120";
            else if (this.code.equals("B001")) number = "200";
            else if (this.code.equals("B002")) number = "210";
    
            return number; //생성자로 받아온 Exception code를 비교해 해당 문자열을 리턴하는 메서드
        }
    }