본문 바로가기

Development/JAVA

(Java) Chapter12. 예외

1. 개념

  1) 개괄

    어플리케이션을 개발하는 데 있어서 오류나 보안 등의 약점으로 문제가 발생할 수 있는데, 이를 실패라고 한다면 최대한 덜 실패하는 방법을 알기 위해서 예외를 알고 있어야 한다. 기능이 많아질수록 오류가 발생할 확률은 증가한다. 예외는 프로그램을 만든 개발자가 상정한 정상적인 처리에서 벗어나는 경우에 이를 처리하기 위한 방법이다. 

  2) 사용 문법 요소

 try {

    예외의 발생이 예상되는 로직

} catch(예외클래스 인스턴스) {
    예외가 발생했을 때 실행되는 로직

}

  즉, 코드가 실행되는 도중 문제가 발생한다면 예외적인 사항으로 인식하고 매개변수 'e'에 담고 다른 구문을 실행

class Calculator10 {
    int left, right;
    public void setOprands(int left, int right) {
        this.left = left;
        this.right = right;
    }
    public void divide() {
        try {
            System.out.println("계산결과는 ");               // 실행
            System.out.println(this.left / this.right);   // 에러
            System.out.println(" 입니다.");                 // 실행하지 않고 catch 로 넘어감
        } catch (Exception e) {                           // 에러사항 변수에 담음
            System.out.println("\n\ne.getMessage()\n" + e.getMessage()); // 실행
        }
        System.out.println("Divide End");                 // 에러에 대한 메세지 출력 후 빠져나와 실행
    }
}
public class _01_Exception1 {
    public static void main(String[] args) {
        Calculator10 c1 = new Calculator10();
        c1.setOprands(10, 0);
        c1.divide();
    }
}

/*
계산결과는 


e.getMessage()
/ by zero
Divide End
 */

  3) 다양한 예외들과 다중캐치 - 배열 변수에 대한 예외

class C {
    private int[] arr = new int[3];
    C() {
        arr[0] = 0;
        arr[1] = 10;
        arr[2] = 20;
    }
    public void z(int first, int second) {
        try {
            System.out.println(arr[first] / arr[second]);
        } catch(ArithmeticException e) {                    // 예외상황 1에 대한 실행 명령
            System.out.println("ArithmeticException e");
        } catch (ArrayIndexOutOfBoundsException e) {        // 예외상황 2에 대한 실행 명령
            System.out.println("ArrayIndexOutOfBoundsException e");
        } catch (Exception e) {                             // 예외의 포괄이기 때문에 먼저 나올 수 없음
            System.out.println("Variable error");
        }
    }
}
public class _02_MultipleCatch {
    public static void main(String[] args) {
        C c = new C();
        c.z(10, 1);
    }
}

  4) finally

try {

    예외의 발생이 예상되는 로직

} catch(예외클래스 인스턴스) {
    예외가 발생했을 때 실행되는 로직

} finally {

    예외여부와 관계없이 실행되는 로직

}

class C {
    private int[] arr = new int[3];
    C() {
        arr[0] = 0;
        arr[1] = 10;
        arr[2] = 20;
    }
    public void z(int first, int second) {
        try {
            System.out.println(arr[first] / arr[second]);
        } catch(ArithmeticException e) {                    // 예외상황 1에 대한 실행 명령
            System.out.println("ArithmeticException e");
        } catch (ArrayIndexOutOfBoundsException e) {        // 예외상황 2에 대한 실행 명령
            System.out.println("ArrayIndexOutOfBoundsException e");
        } catch (Exception e) {                             // 예외의 포괄이기 때문에 먼저 나올 수 없음
            System.out.println("Variable error");
        } finally {
            System.out.println("finally");                  // 매 결과값마다 다음에 ""문구를 출력함
        }
    }
}
public class _02_MultipleCatch {
    public static void main(String[] args) {
        C c = new C();
        c.z(10, 1);          // 비정상
        c.z(1, 0);           // 비정상
        c.z(2, 1);           // 2
    }
}

  - 사용 예 : 데이터베이스에 접속한 어플리케이션이 정상 유무와 관계없이 작동하고 있다면 동작을 마무리하고 접속 해제하기 위한 경우

 

  5) 예외의 강제 (FileReader)

  - FileReader 와 throws

public FileReader(String filename)
    throws FileNotFoundException;  // FileReader API를 사용하는 경우 반드시 예외처리를 해야하는 제약이 있음

  ※ 강제될 경우 throws 키워드를 사용함

class A3 {
    void run() {
    }
}
class B3 {
    void run() {
        A3 a = new B3();
        a.run();
    }
}
public class _03_Throw1 {
    public static void main(String[] args) {
        B3 b = new B3();
        b.run();
    }
}

    A3에서 예외가 생기면 다음과 같은 예외 사슬이 발생한다

    A3 > B3 > throws > 일반 사용자

    throws에서 일반사용자로 예외가 전가되면 예외를 처리하지 않고 일반 사용자가 그대로 예외

 

  - 책임의 전가

package chapter_11;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

class A3 {
    void run() throws FileNotFoundException, IOException {  // 아래의 주석부분을 다음 클래스로 보내려면 이와깉이 throws 로 책임 전가
        BufferedReader bReader = null;
        String input = null;
//        try {
//            bReader = new BufferedReader(new FileReader("out.txt"));
//        } catch (FileNotFoundException e) {
//            e.printStackTrace();
//        }
//        try {
//            input = bReader.readLine();
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
        System.out.println(input);
    }
}
class B3 {
    void run() {
        A3 b = new A3();
        try {
            b.run();                    // class A3에서 나타난 run 메소드에 발생한 두 예외가 전가되어 예외 책임 져야함
        } catch (FileNotFoundException e) {  // 그래서 클래스 B3에서 처리함. 만약 또 사용자인 메인메소드로 넘기려면 A3와 똑같이 함
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class _03_Throw1 {
    public static void main(String[] args) {
        B3 b = new B3();
        b.run();
    }
}

  ※ 만약 읽어들일 파일이 없으면 메인메소드 catch단에서 printout으로 "파일이 없습니다." 처리를 한다면 에러메세지가 뜨지 않고 정의된 값을 출력함. 즉, 에러가 뜨면 예외를 처리하지 않았다는 뜻

 

  - 예외 만들기

    이번 장 첫 예제를 이용해 API 생산자의 입장에서 예외 만들어보자.

package chapter_11;
class Calculator11 {
    // 두 가지 예외상황 설정 방법
    int left, right;
    public void setOprands(int left, int right) {
//        if(right == 0) {
//            throw new IllegalAccessException("두번째 인자는 0을 허용하지 않습니다.");
//        }                        // 전역변수에 대한 예외 설정 방법
        this.left = left;
        this.right = right;
    }
    public void divide() {
        if(right == 0) {
            throw new ArithmeticException("0으로 나눌 수 없습니다.");
        }                        // 메소드에 대한 예외 방법
        try {
            System.out.println("계산결과는 ");               // 실행
            System.out.println(this.left / this.right);   // 에러
            System.out.println(" 입니다.");                 // 실행하지 않고 catch 로 넘어감
        } catch (Exception e) {                           // 에러사항 변수에 담음
            System.out.println("\n\ne.getMessage()\n" + e.getMessage()); // 실행
            System.out.println("\n\ne.toString()\n" + e.toString());
            System.out.println("\n\ne.printStackTrace()");
        }
        System.out.println("Divide End");                 // 에러에 대한 메세지 출력 후 빠져나와 실행
    }
}
public class _04_ExceptionMaking {
    public static void main(String[] args) {
        Calculator11 c1 = new Calculator11();
        c1.setOprands(10, 0);
        try {
            c1.divide();
        } catch (ArithmeticException e) {               // 클래스의 throws 의 값을 가져옴
            System.out.println(e.getMessage());
        }
        c1.divide();
    }
}

 

  ※ 주요 Exception 리스트

예외 사용해야 할 상황
IllegalArgumentException 매개변수가 의도하지 않은 상황을 유발시킬때
IllegalStateException 메소드를 호춯하기 위한 상태가 아닐 때
NullPointerException 매개 변수 값이 null일 때
IndexOutOfBoundsException 인덱스 매개 변수 값이 범위를 벗어날 때
ArithmeticException 산술적인 연산에 오류가 있을 때

  6) 예외의 상속관계(Throwable 클래스)

Unchecked Exception (굳이 Throw로 처리할 필요 없음) Checked Exception (반드시 Throw를 통해 처리)
java.lang.Object
java.lang.Throwable
java.lang.Exception
java.lang.RuntimeException
java.lang.ArithmeticException
java.lang.Object
java.lang.Throwable
java.lang.Exception
java.io.IOException

  - 위의 순서는 포괄적인 정도에 따라 나열한 순서임(포괄 > 지협)

  - Throwable의 예외 종류

    Error: 기반시스템(버츄얼머신)의 문제로 로직의 문제는 아님

    Exception: IOException + RuntimeException(하위에 ArithmeticException)

 

  7) 나만의 예외 만들기

  - 예외를 checked로 할 것인지 unchecked로 할 것인가를 정해야 함

  - checked 예외: API 쪽에서 예외를 던졌을 때 API사용자 쪽에서 예외 상황을 복구할 수 있다면 사용, 사용자에게 문제 해결 기회를 주는 것이면서 예외처리를 강제함

  - unchecked 예외: 사용자가 API의 사용방법을 어겨서 발생하는 문제거나 예외 상황이 이미 발생한 시점에서 그냥 프로그램을 종료하는 것이 덜 위험할 때.

  - 예시: 파일을 찾을 때 파일을 못찾았다면, 프로그램을 종료해버리는게 좋을테니 unchecked를 주로 사용함.

  - RuntimeException으로 던지는 경우

package chapter_11;
class DivideException extends RuntimeException {
    DivideException() {                       // 생성자를 만들면 기본생성자를 만들어주지않으므로 직접 구현
        super();
    }
    DivideException(String message) {         // 생성자는 직접 만들어줘야함
        super(message);
    }
}
class Calculator11 {
    // 두 가지 예외상황 설정 방법
    int left, right;
    public void setOprands(int left, int right) {
//        if(right == 0) {
//            throw new IllegalAccessException("두번째 인자는 0을 허용하지 않습니다.");
//        }                        // 전역변수에 대한 예외 설정 방법
        this.left = left;
        this.right = right;
    }
    public void divide() {
        if(right == 0) {
            throw new DivideException("0으로 나눌 수 없습니다.");
            // throw new ArithmeticException("0으로 나눌 수 없습니다.");
        }                        // 메소드에 대한 예외 방법
        try {
            System.out.println("계산결과는 ");               // 실행
            System.out.println(this.left / this.right);   // 에러
            System.out.println(" 입니다.");                 // 실행하지 않고 catch 로 넘어감
        } catch (Exception e) {                           // 에러사항 변수에 담음
            System.out.println("\n\ne.getMessage()\n" + e.getMessage()); // 실행
            System.out.println("\n\ne.toString()\n" + e.toString());
            System.out.println("\n\ne.printStackTrace()");
        }
        System.out.println("Divide End");                 // 에러에 대한 메세지 출력 후 빠져나와 실행
    }
}
public class _04_ExceptionMaking {
    public static void main(String[] args) {
        Calculator11 c1 = new Calculator11();
        c1.setOprands(10, 0);
        try {
            c1.divide();
        } catch (ArithmeticException e) {               // 클래스의 throws 의 값을 가져옴
            System.out.println(e.getMessage());
        }
        c1.divide();
    }
}

  - Exception

package chapter_11;
class DivideException extends Exception {
    public int left;
    public int right;
    DivideException() {                       // 생성자를 만들면 기본생성자를 만들어주지않으므로 직접 구현
        super();
    }
    DivideException(String message) {         // 생성자는 직접 만들어줘야함
        super(message);
    }
    DivideException(String message, int left, int right) {         // 생성자는 직접 만들어줘야함
        super(message);
        this.left = left;
        this.right = right;
    }
}
class Calculator11 {
    // 두 가지 예외상황 설정 방법
    int left, right;
    public void setOprands(int left, int right) {
//        if(right == 0) {
//            throw new IllegalAccessException("두번째 인자는 0을 허용하지 않습니다.");
//        }                        // 전역변수에 대한 예외 설정 방법
        this.left = left;
        this.right = right;
    }

    public void divide() throws DivideException {
        if (right == 0) {
            throw new DivideException("0으로 나눌 수 없습니다.", this.left, this.right);
        }
        System.out.println(this.left / this.right);
    }
}
//    public void divide() {
//        if(right == 0) {
//            throw new DivideException("0으로 나눌 수 없습니다.");
//            // throw new ArithmeticException("0으로 나눌 수 없습니다.");
//        }                        // 메소드에 대한 예외 방법
//        try {
//            System.out.println("계산결과는 ");               // 실행
//            System.out.println(this.left / this.right);   // 에러
//            System.out.println(" 입니다.");                 // 실행하지 않고 catch 로 넘어감
//        } catch (Exception e) {                           // 에러사항 변수에 담음
//            System.out.println("\n\ne.getMessage()\n" + e.getMessage()); // 실행
//            System.out.println("\n\ne.toString()\n" + e.toString());
//            System.out.println("\n\ne.printStackTrace()");
//        }
//        System.out.println("Divide End");                 // 에러에 대한 메세지 출력 후 빠져나와 실행
//    }
//}
public class _04_ExceptionMaking {
    public static void main(String[] args) {
        Calculator11 c1 = new Calculator11();
        c1.setOprands(10, 0);
        try {
            c1.divide();
        } catch (DivideException e) {               // 클래스의 throws 의 값을 가져옴
            System.out.println(e.getMessage());
            System.out.println(e.left);
            System.out.println(e.right);
        }
    }
}

'Development > JAVA' 카테고리의 다른 글

(Java) Chapter14. enum  (0) 2023.01.29
(Java) Chapter13. Object  (0) 2023.01.23
(Java) Chapter11. 접근 제어자  (0) 2023.01.17
(Java) Chapter10. API  (0) 2023.01.10
(Java) Chapter8. 생성자와 상속  (0) 2023.01.10