예외처리

cutiepazzipozzi·2023년 5월 9일
1

지식스택

목록 보기
26/35
post-thumbnail

[예외처리]

프로그램 오류

= 프로그램이 실행 중 어떠한 원인으로 인해 오작동하거나 비정상적으로 종료되는 원인

  • 발행 시점에 따라 컴파일 에러런타임 에러로 나뉨

  • 컴파일 에러 = 컴파일 할 때 발생하는 에러

  • 런타임 에러 = 프로그램의 실행도중 발생하는 에러

  • 컴파일러가 소스코드의 기본적인 사항은 컴파일 시 걸러줄 수 있으나, 실행 도중 발생할 수 있는 잠재적인 오류는 검사할 수 X
    => 컴파일이 되어도 에러에 의해 잘못된 결과를 얻거나 프로그램이 비정상적으로 종료될 수도 있음!

- 실행 시(runtime) 발생하는 오류를 에러예외로 구분하였음!!
=> 에러 = 발생하면 복구할 수 없는 심각한 오류 (프로세스에 영향을 줌)
=> 예외 = 발생하더라도 수습될 수 있는 오류 (스레드에 영향을 줌)

예외처리의 정의와 목적

  • 예외처리 = 프로그램 실행 시 발생할 수 있는 예기치 못한 예외 발생에 대비한 코드 작성
  • 목적 = 예외로 인한 실행 중인 프로그램의 갑작스런 비정상 종료를 막고, 정상적인 실행 상태를 유지할 수 있도록 하는 것
  • 예외처리 구문
try {
	//예외가 발생할 가능성이 있는 문장을 적어줌
} catch(Exception e) {
	//이 e가 발생했을 때 처리하기 위한 문장을 적음
}
... 다른 catch~~

-> catch문은 여러개가 올 수 있는데, 해당되는 한 개의 catch block만 수행된다.

class Exception3 {
	public static void main(String[] args) {
    	int num = 100;
        int res = 0;
        
        for(int i = 0; i < 10; i++) {
        	try {
            	res = num / (int) (Math.random()*10);
                //여기서 만약 Math.random()의 값이 0이라면
                //ArithmeticException 예외가 발생
                System.out.println(res);
            } catch (ArithmeticException e) {
            	System.out.println("0");
            }
        }
    }
}

예외의 종류

  1. Checked Exception (Exception 클래스에서 RuntimeException 클래스+ 그 자손 클래스들 제외)
    = 컴파일 시에 무조건 예외 체크를 해줘야 하는 예외들
    = 로직에 문제가 생기면 발생할 수 있는 에러
  2. Error
    = 프로그램 밖에서 발생하는 예외
  3. Runtime Exception
    = 프로그래머의 부주의로 주로 발생하는

2, 3을 묶어서 우리는 흔히 Unchecked Exception이라고 한다!
(왜 unchecked라고 하냐면, 컴파일 시에 체크를 안해서! 그래서 런타임 시에 에러가 발생할 수 있는,,)

try-catch문에서의 흐름

  1. 예외가 발생한 경우

    1. 발생한 예외와 일치하는 catch 블럭이 있는지 확인
    2. catch 블럭을 찾는다면, 이 블럭 내의 문장들을 수행하고 try-catch문을 빠져나와 문장을 계속해서 수행
  2. 발생하지 않은 경우

    catch문을 거치지 않고 try-catch문을 빠져나감

예외 발생시키기

throw 키워드를 활용해 예외를 고의로 발생시켜 보자.

  1. new 연산자를 이용해 예외 클래스의 객체를 만듦
  2. throw 키워드를 이용해 예외를 발생시킴
class ExceptionEx {
	public static void main(String[] args) {
    	try {
        	Exception e = new Exception("고의로 예외 발생!");
            throw e;
        } catch (Exception e) {
        	System.out.println("에러 메시지: "+e.getMessage());
            e.printStackTrace();
        }
        System.out.println("프로그램 정상 종료");
    }
}

예외 클래스 계층구조

실행 시 발생하는 예외와 오류를 클래스로 정의하였다.

위의 그림과 같이 예외 클래스는 두 개의 그룹으로 나뉜다.

  • RuntimeException과 그 자손
    (프로그래머의 실수에 의해서 발생될 수 있는 예외
    ex. 배열의 범위를 벗어난)
  • Exception 클래스와 그 자손
    (외적인 요인에 의해 발생하는 예외
    ex. 입력한 데이터 형식이 잘못됐을 때)

두 그룹의 차이점은 컴파일시의 예외처리 체크여부이다.
RuntimeException 클래스 그룹에 속하는 예외가 발생할 가능성이 있는 코드는 예외 처리를 해주지 않아도 컴파일 시에 문제가 되지 않으나, Exception 클래스 그룹에 속하는 예외가 발생할 가능성이 있는 코드는 반드시 예외 처리를 해줘야 한다!!!!
(우리가 소위 알고 있는 unchecked, checked 예외)

근데 왜 RuntimeException 클래스에 속하는 예외들에는 예외처리를 굳이 안해줘도 되는 걸까?? 여기까지 예외처리를 해줘야 한다면 모든 참조 변수와 배열이 사용되는 곳에도 예외처리를 해줘야 하기 때문이다..
(프로그래머의 잘못된 사용으로 발생하는 예외가 대다수인데 이는 예측하기 어렵기 때문에.. 라는 이유도 있대용..)

0/0을 예로 들면 굳이 try-catch로 예외처리를 하는것보다 애초에 프로그래밍을 할 때 분모를 0으로 두지 않도록 설정하는 것이 더 바람직하지 않은가?

한 수 앞을 내다본 나의 예외처리 포스팅

예외 발생과 catch 블럭

catch 블럭 -> (){} 두 블럭으로 나뉨

  • () = 처리하고자 하는 예외와 같은 타입의 참조변수를 선언해야함
  • 첫번째 catch 블럭부터 차례대로 내려가면서 괄호 내에 선언된 참조변수의 종류와 생성된 예외 클래스의 인스턴스에 instanceOf 연산자를 활용해 검사 (여기서 true가 나오는 블럭이 있을 때 까지 검사 진행)
class Exception10 {
	public static void main(String[] args) {
    	System.out.println(1);
        System.out.println(2);
        try {
        	System.out.println(3);
            System.out.println(0/0);
            System.out.println(4);
        } catch (Exception e) {
        //0/0에서 ArithmeticException이 발생하는데
        //이 예외는 Exception의 자손 클래스이므로 true를 반환함
        	System.out.println(5);
        }
        System.out.println(6);
    }
}

//실행 결과
1
2
3
5
6

이처럼 예외가 발생했을 때 생성되는 인스턴스에는 발생한 예외에 대한 정보가 담겨있다. 이 정보는 getMessage()printStackTrace()를 활용해 얻을 수 있다.

  • getMessage() = 발생한 예외 클래스의 인스턴스에 저장된 메시지를 얻음
  • printStackTrace() = 예외 발생 시 호출스택에 있던 메서드의 정보와 예외 메시지를 화면에 출력

이들은 Throwable 클래스에 선언되어 있으며 Exception 클래스에서 위의 메서드 외에도 총 10가지의 메서드를 오버라이딩하여 에러 메시지를 넘겨준다.

++ System.err은 System 클래스의 static 멤버로서 PrintStream 클래스 타입의 참조변수로, 출력방향이 화면으로 설정되어 있다.
++ 여기서 setErr() 메서드를 활용해 출력방향을 error.log라는 파일로 변경할 수 있다.

finally 블럭

= 예외의 발생여부에 관계없이 실행되어야 할 코드

try {
	// 예외가 발생할 가능성이 있는 문장
} catch() {
	// 예외처리를 위한 문장
} finally {
	// 예외의 발생여부에 관계없이 수행되어야 할 문장
}

실제 예시

class FinallyTest {
	public static void main(String[] args) {
    	FinallyTest3.method1();
        System.out.println("method1()의 수행을 마치고 main메서드로 돌아왔습니다.");
    }
    
    static void method1() {
    	try {
        	System.out.println("method1()이 호출되었습니다.");
            return;
        } catch (Exception e) {
        	e.printStackTrace();
        } finally {
        	System.out.println("method1()의 finally 블럭이 실행되었습니다");
        }
    }
}

//출력
method1()이 출력되었습니다.
method1()finally블럭이 실행되었습니다.
method1()의 수행을 마치고 main메서드로 돌아왔습니다.
  • try 블럭에서 return문이 실행되는 경우도 finally 블럭의 문장들이 먼저 실행된 후, 현재 실행중인 메서드를 종료!
  • catch 블럭의 문장 수행 중 return문을 만나도 finally 블럭의 문장들은 수행됨

메서드에 예외 선언하기

우리가 주로 사용하는 예외 처리 방식인 try-catch문 외에도 예외를 선언하는 방식이 있다. 그것은 바로 메서드의 선언부에 throws 키워드를 사용하여 메서드 내에서 발생할 만한 예외를 적어주는 방식이다.

  • 기존의 많은 언어들은 메서드에 예외선언을 하지 않아, 어떤 상황에 어떤 종류의 예외가 발생할 지 몰라 대비를 하는 것이 어려웠지만 자바는 미리 명시하여 처리를 강요하기 때문에 견고한 프로그램 코드를 작성할 수 있다!

  • void method() throws Exception1, Exception2, ... {내용}

  • 예외를 메서드의 throws에 명시하는 것은 예외를 처리하는 것이 아니라, 예외가 발생할 가능성이 있는 메서드를 호출한 메서드에게 예외를 전달하여 예외처리를 맡기는 것이다!!

class ExceptionEx10 {
	public static void main(String[] args) {
    	try {
        	method1();
        } catch (Exception e) {
        	System.out.println("main메서드에서 예외가 처리되었습니다.");
           	e.printStackTrace();
        }
    }
    
    static void method1() throws Exception {
    	throw new Exception();
    }
}

//출력
main메서드에서 예외가 처리되었습니다.
java.lang.Exception
	at ExceptionEx10.method1(코드라인~)
    at ExceptionEx10.main(코드라인~)

위의 예시는 예외가 발생한 메서드(method)에서 예외를 처리하지 않고 호출한 메서드(main)로 넘겨주어, 호출한 메서드가 예외가 발생한 메서드를 사용한 라인에서 예외가 발생한 것으로 간주하고 처리한다.

이 경우 외에도 예외가 발생한 메서드 자체에서 예외를 처리할 수도 있다.

예외 되던지기

한 메서드 내에서 발생할 수 있는 예외가 여러개일 때, 몇개는 try-catch문을 통해, 나머지는 선언부에 지정하여 호출한 메서드에서 처리하도록 할 수 있다.

이 방법은 예외를 처리한 후 인위적으로 다시 발생시키는 방법을 통해 가능한데, 이를 예외 되던지기(exception re-throwing)이라 부른다.

  1. 예외 발생 가능성이 있는 메서드에서 try-catch문을 활용해 예외를 처리
  2. catch문에서 필요한 작업을 취한 뒤 throw문을 사용해 예외 재발생
  3. 재발생된 예외는 메서드를 호출한 메서드에게 전달되고 호출한 메서드의 try-catch문에서 예외를 다시 처리

다만 이 방법은 한 예외에 대해 발생한 메서드, 호출한 메서드 모두 처리해줘야 할 작업이 있을 때만 사용!!! ※

class Exception23 {
	public static void main(String[] args) {
    	try {
        	method1();
        } catch (Exception e) {
        	System.out.println("main메서드에서 예외가 처리되었습니다.");
        }
    }
    
    static void method1() throws Exception {
    	try {
        	throw new Exception();
        } catch (Exception e) {
        	System.out.println("method1메서드에서 예외가 처리되었습니다.");
            throw e;
        }
    }
}

//실행결과
method1메서드에서 예외가 처리되었습니다.
main메서드에서 예외가 처리되었습니다.

사용자정의 예외 만들기

필요에 따라 사용자가 직접 예외 클래스를 새로 정의해 사용할 수 있다.
보통의 경우는 Exception 클래스를 상속받아 사용한다!

class MyException extends Exception {
	private final int ERR_CODE;
    
    MyException(String msg, int errCode) {
    	super(msg);
        ERR_CODE = errCode;
    }
    
    MyException(String msg) {
    	this(msg, 100);
    }
    
    public int getErrCode() {
    	return ERR_CODE;
    }
}

마지막으로,,

++ 추가적으로 읽어보면 좋을 예외처리 공식문서

참고

사진->https://finewoo.tistory.com/22
https://velog.io/@jsj3282/%EC%9E%90%EB%B0%94%EC%9D%98-%EC%98%88%EC%99%B8%EC%9D%98-%EC%A2%85%EB%A5%98-3%EA%B0%80%EC%A7%80
자바의 정석, 2nd Edition.

profile
노션에서 자라는 중 (●'◡'●)

0개의 댓글