자바 예외처리 기본

황상익·2023년 12월 15일

열혈 자바

목록 보기
15/30

자바에서 말하는 예외
예외는 단순한 문법적 오류가 아닌, 실행 중간에 발생하는 정상적이지 않은 상황.

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("a/b....a?");
        int n1 = sc.nextInt();
        System.out.println("a/b....a?");
        int n2 = sc.nextInt();
        System.out.printf("%d / %d = %d \n", n1, n2 , n1/n2);
        System.out.println("GoodBye");

    }
}

0이 될 수 없음에도 불구하고 0을 입력한 프로그램 사용자에게 있다. 이러한 상황을 가리켜 예외 라고 한다. 가상머신은 예외가 발생하면, 그에 대한 내용을 간단히 출력하고 프로그램을 종료. 숫자가 아닌 문자를 입력하는 행위 역시 예외라고 한다.
예외의 처리를 위한 try ~ catch
ArithmeticException -> 숫자 연산에서의 오류 상황.
InputMismatchException -> 입력에서의 오류 상황
자바는 예외 상황별로 그 상황을 알리기 위한 클래스를 정의. -> 예외 클래스라고 한다. 각 예외에 대한 인스턴스를 처리하면 그 프로그램은 예외를 처리 한 것으로 간부하여 프로그램을 종료 하지 않는다. 하지만 이 인스턴스를 처리하지 않으면, 프로그램은 종료된다.
Try {
관찰영역
} catch (Exception e) {
처리영역
}
Try 에서 발생한 예외 상황을, catch 영역에서 처리한다.
Try 영역의 실행 중간에 예외 상황이 만들어지고, 가상머신이 인스턴스를 생성하면, 이 인스턴스는 메소드를 호출하듯이 catch 구문의 매개변수 e에 전달된다. 가상머신은 catch 구문 안에서, 무엇을 하든 상관없이 예외가 처리된 것으로 간주 -> 실행

public class Main1 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        try {
            System.out.println("a/b....a?");
            int a = sc.nextInt();
            System.out.println("a/b....a?");
            int b = sc.nextInt();
            System.out.printf("%d / %d = %d \n", a, b , a/b);
        } catch (Exception e){
            System.out.println(e.getMessage());
        }

        System.out.println("Bye");
    }
}

가상머신은 예외에 대한 인스턴스를 생성한다. 예외 발생 지점을 try 영역에 이어서 등장하는 catch 영역에서 인스턴스를 인자로 전달 받을 수 있는지 확인, 받을 수 있으면 catch 영역으로 인스턴스를 전달. Catch 영역으로 예외 인스턴스가 전달되면, 가상머신은 예외가 처리된 것으로 간주. 그 후에 출력문을 출력.

Try로 감싸야할 영역의 결정
Try {
1.
2. 예외 발생지점
3.
}
Catch (Exception e) {
}
예외 처리 이후 실행 지점
숫자 3위치에서 실행을 이어가는 것이 아닌, try – catch 문 전체를 건너뛰어 4의 위치에서 실행한다. -> 이는 관련 있는 작업들을 하나로 묶는데 도움이 된다.

public class Main2 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        try {
            System.out.println("a/b....a?");
            int a = sc.nextInt();
            System.out.println("a/b....a?");
            int b = sc.nextInt();
            System.out.printf("%d / %d = %d \n", a, b, a / b);
        } catch (ArithmeticException e) {
            System.out.println(e.getMessage());
        } catch (InputMismatchException e){
            System.out.println(e.getMessage());
        }// catch (ArithmeticException | InputMismatchException e){

        System.out.println("Bye");
    }
}

System.out.println("a/b....a?");
int a = sc.nextInt();
System.out.println("a/b....a?");
int b = sc.nextInt();
System.out.printf("%d / %d = %d \n", a, b, a / b);
하나의 작업에서 예외 가능 -> 한 곳에서 예외가 발생하면, 나머지 부분을 건너뛰는 것이 적절

둘 이상의 예외를 처리하기 위한구성
둘에 대해서 모두 예외 처리를 하고자 한다면, catch 구문 둘을 이어서 구성하면 된다.
} catch (ArithmeticException e) {
System.out.println(e.getMessage());
} catch (InputMismatchException e){

Throwable 클래스와 예외 처리의 책임전가 .
getMessage() -> 예외의 원인을 담고있는 문자열 반환
printStackTrace() -> 발생한 위치와 호출된 메소드 정보 출력

public class Main3 {
    public static void main(String[] args) {
      try{
          md1(3);
      } catch (Throwable e){
          e.printStackTrace();
      }
        System.out.println("Bye");
    }

    public static void md1(int n){
        md2(n , 0);
    }

    public static void md2(int n1, int n2){
        int r = n1 / n2;
    }
}

호출 순서 -> main, md1, md2와 같다
Md2에서 예외 발생 했지만 해당 예외를 처리하지 않았다. 이럴때는 md2d를 호출한 md1에게 책임을 넘김, md1도 예외 처리 하지 않았으면, main에서 예외를 처리한다. Main 조차 예외를 처리하지 않았다면, 가상머신이 대신 예외를 처리한다. 그러나 실제로 넘어오는 예외는 Throwable이 아니다. 모든 예외 클래스는 Throwable을 상속하므로, 상속 관계에 의해 md2에서 발생한 예외를 위와 같이 처리할 수 있다. 그리고 catch 구문에서 호출한 printStackTrace 메소드가 출력된 내용을 보면, 가상머신이 예외를 처리할 때 출력한 문장과 유사함을 알 수 있다.

예외상황을 알리기 위해 정의된 클래스 종류
public static void main(String[] args) {
int[] arr = {1,2,3};
for (int i = 0; i < 4; i++) {
System.out.println(arr[i]);
}
}
}
 ArrayIndexOutOfBoundsException

class Boar {
}

class PBorad extends Boar{

}

public class Main5 {
    public static void main(String[] args) {
        Boar boar = new PBorad();
        PBorad pBorad = (PBorad) boar;

        System.out.println("Intermediate Location");
        Boar eb = new Boar();
        PBorad pb = (PBorad) eb;
    }
}

 ClassCastException

public class Main6 {
    public static void main(String[] args) {
        String str = null;
        System.out.println(str);
        int len = str.length();
    }
}

 nullPointExcetion

예외처리에 대한 나머지 설명
최상위 클래스가 Throwable임은 앞서 설멍.
 Error 클래스
 Exception 클래스
 RuntimeException 클래스 -> Exception 클래스를 상속
RuntimeException 클래스를 상속하는 예외 클래스
 ArithmeticException
 ClassCastException
 IndexOutOfBoundsException
 NegativeArraySizeException
 NullPointerException
 ArrayStoreException
Exception 클래스를 상속하는 예외 클래스가 있는데, RuntimeException 클래스를 직접 혹은 간접적으로 상속하지 않고, Exception 클래스만 상속하는 예외 클래스에 해당하는 내용

Exception을 상속하는 예외 클래스의 예외처리

IOExcpetion 
public class Main7 {
    public static void main(String[] args) {
        Path file = Paths.get("C");
        BufferedWriter bw = null;

        try {
            bw = Files.newBufferedWriter(file);
            bw.write('a');
            bw.write('b');

            if (bw != null){
                bw.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public class Main8 {
    public static void main(String[] args) {
        try {
            md1();
        } catch (IOException e){
            e.printStackTrace();
        }
    }

    public static void md1() throws IOException {
        md2();
    }

    public static void md2() throws IOException {
        Path file = Paths.get("C");
        BufferedWriter bw = null;

        bw = Files.newBufferedWriter(file);
        bw.write('a');
        bw.write('b');

        if (bw != null){
            bw.close();
        }
    }
}

md2 내에서 IOException 예외 발생할 수 있다. 이럴 때 try ~ catch 문을 작성하거나, 예외처리를 이 메소드를 호출한 메소드에게 넘긴다는 표시를 해야 한다.
throws IOException
 예외가 메소드 내에서 발생 할 경우, md2 호출한 영역으로 예외를 처리를 넘긴다는 뜻
그리고 예외를 넘기는 순간 md2는 종료
따라서 IOException 예외가 전달될 때 md1을 호출하므로, main 메소드 선택도 둘중 하나
 Try ~ catch문을 통해서 IOExecption을 처리
 예외 처리를 직접 넘기거나
Error를 상속하거나 RuntimeException을 상속하는 예외 발생은 코드작성에서 특별하지 않아도 된다. 그러나 Exception을 상속하는 예외의 발생에 대해서는 try ~ catch 문을 통해서 예외를 처리, throws 선언을 통해서 예외의 처리를 넘긴다는 표시를 꼭 해야한다. 예외 대부분은 Exception을 상속

프로그래머가 정의하는 정의

class ReadAgeExcepton extends Exception{
    public ReadAgeExcepton(){
        super("유효하지 않은 나이가 입력되었습니다");
    }
}

public class Main9 {
    public static void main(String[] args) throws ReadAgeExcepton {
        System.out.println("나이 입력");
        try {
            int age = readAge();
            System.out.printf("입력된 나이: %d \n" , age);
        } catch (ReadAgeExcepton e){
            System.out.println(e.getMessage());
        }
    }

    public static int readAge() throws ReadAgeExcepton {
        Scanner sc = new Scanner(System.in);
        int age = sc.nextInt();

        if (age < 0) {
            throw new ReadAgeExcepton();
        }
        return age;
    }
}

프로그래머가 정의하는 예외
Exception을 상속하는 점을 제외하면 일반 클래스와 크게 별 차이 없음. 그리고 생성자에서 상위 클래스의 생성자를 호출하면서, 예외 상황에 대한 설명을 담고 있는 문자열을 전달. -> throwable 클래스에 정의 된 다음 메소드 호출 시 반환
class ReadAgeExcepton extends Exception{
public ReadAgeExcepton(){
super("유효하지 않은 나이가 입력되었습니다");
}
}

잘못된 catch 구문의 구성
SecondException, ThirdException, FirstException을 직간접적으로 상속 -> 2,3 번째 catch 구문은 실행 할 일이 없다.

Finally 구문
Try에 이어서 finally 구문을 둘 수도 있다.
Try 안으로 진입하면 finally에 의해 무조건 실행.

public class Main10 {
    public static void main(String[] args) throws IOException {
        Path file = Paths.get("C");
        BufferedWriter writer = null;

        try {
            writer = Files.newBufferedWriter(file);
            writer.write('A');
            writer.write('Z');
        } catch (IOException e) {
            e.printStackTrace();
        }
        finally {
            try {
                if (writer != null){
                    writer.close();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}

try 안으로 들어오면 finally 구문은 반드시 실행. Close 메소드 호출은 반드시 보장
finally 안에서도 try ~ catch 구문 가능
try with resources 구문
finally로 처리 하면 코드 복잡 -> try에 이어 등장하는 소괄호 안에서는 resource라고 쓰여 있는 위치에서는) 종료의 과정을 필요로 하는 리소스 생성할 수 있다. 그 후 자동으로 종료

public class Main11 {
    public static void main(String[] args) throws IOException {
        Path file = Paths.get("C");

        try (BufferedWriter writer1 = Files.newBufferedWriter(file)) {
            writer1.write('a');
            writer1.write('z');
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

try – resource 구문
try (BufferedWriter writer1 = Files.newBufferedWriter(file)) {
writer1.write('a');
writer1.write('z');
} catch (IOException e) {
e.printStackTrace();
}
참조변수 writer가 참조하는 인스턴스의 종류는 신경쓰지 않아도 된다. Try 안에서의 예외 발생한건 writer를 대상으로 한 메소드 호출은 보장되기 때문이다
Writer.close을 넣지 않아도 된다. Try with resources문에서 호출하는 메소드는 AutoCloseable 인터페이스의 close 메소드 이다.

profile
개발자를 향해 가는 중입니다~! 항상 겸손

0개의 댓글