본문 바로가기

JAVA

예외 처리 Exception Handling

1. 프로그램 오류의 종류

 

프로그램을 실행 중 비정상적으로 종료된 경우에 대하여 그 발생 시점을 기준으로 '컴파일 에러'와 '런타임 에러'라고 한다.

에러 종류 발생 시점 내용
컴파일 에러 컴파일 시 발생 오타, 잘못된 구문, 자료형 체크 등의 기본적인 문법 검사를 실행한다.
런타임 에러 실행 시 발생 실행 도중 발생할 수 있는 잠재적 오류
발생할 수 있는 모든 경우의 수에 대한 처리를 해야 한다.
논리적 에러 실행 후 발생 컴파일도 되고, 실행도 되지만 원하는 방향의 동작을 하지 않는 경우이다.

이 중에서도 실행 시에 발생할 수 있는 에러 중

비정상적인 종료를 미리 작성한 코드로 인하여 막을 수 있는 것을 '예외 Exception'라고 한다.

예외는 코드로 제어가 어느 정도 가능하기 때문에 발생 가능한 경우의 수를 고려하여 대처하는 '예외 처리'가 필요하다.

 

 

2. 예외 클래스의 계층 구조

 

예외 클래스는 두 가지 그룹으로 분류할 수 있다.

1. Exception 클래스와 그 자손 : FileNotFoundException, ClassNotFoundException, DataFormatException 등
2. RuntimeException 클래스와 그 자손 : IndexOutOfBoundException, NullPointerException, ClasscastException,                                                                   ArtimeticException 등

Exception 클래스 계열의 예외는 보통 사용자의 실수 같은 비교적 외적인 요인으로 인한 예외인 경우가 많고, 

RuntimeException 클래스 계열의 예외는 개발자의 실수로 인한 요인으로 발생하는 경우가 많다.

 

또한 Exception 클래스를 상속 받는 경우에는 'Checked Exception' 으로 반드시 예외 처리 코드를 작성해줘야 하며,

RuntimeException 클래스는 'Unchecked Exception'으로 예외 처리를 반드시 할 필요는 없다.

초기의 자바에서는 Checked Exception을 통해 예외 처리를 강제하는 편이었으나, 

반드시 처리해야 하는 부담에 현대에는 Unchecked Exception 으로 처리하는 경우가 조금 늘었다.

 

 

3. try - catch 문

 

만일 예외를 잡지 못 한다면 프로그램은 시스템을 보호하기 위하여 강제 종료를 하고,

이에 대한 원인을 JVM의 예외처리기(UncaughtExceptionHandler)가 화면에 출력해준다.

 

이러한 경우 실행 상태가 강제적으로 종료되기 때문에 예외가 발생하더라도 실행 상태를 유지하고 싶은 경우가 생긴다.

이럴 때 앞서 말한 예외 처리를 위한 코드를 작성하게 되는 것이고, 이를 try - catch 문으로 구현할 수 있다.

 

try - catch 문은 다음과 같이 작성할 수 있다.

try {
	// 예외가 발생할 가능성이 있는 문장
} catch (Exception1 e) {
	// Exception1이 발생했을 경우, 이를 처리하기 위한 문장
} catch (Exception2 e) {
	// Exception2가 발생했을 경우, 이를 처리하기 위한 문장
} catch (Exception3 e) {
	// Exception3이 발생했을 경우, 이를 처리하기 위한 문장
}

하나의 try 문에는 여러 개의 catch 문을 작성할 수 있는데,

나중에 쓰이는 catch 문의 Exception이 이전에 쓰인 Exception 보다 조상이거나 예외 처리 범위가 넓어야 한다.

🖍 위 쪽의 예외가 처리 범위가 넓으면 아래 쪽 예외는 사용하지 않게 되어 활용성 없는 코드가 될 수 있다.

 

만일 상속 관계가 아닌 에러인 경우에는 수평 배치가 다음과 같이 가능하다.

try {
	// 예외가 발생할 가능성이 있는 문장
} catch (Exception1 e | Exception2 e) {
	// Exception1 또는 Exception2가 발생했을 경우, 이를 처리하기 위한 문장
}

 

try - catch 문은 try 문에서 예외가 발생하는 경우와 발생하지 않는 경우에 따라 흐름이 달라진다.

- try 문에서 예외가 발생하는 경우

  1. 발생한 예외와 일치하는 catch 문이 있는지 확인한다.
  2. 일치하는 catch 문이 있다면,
      catch 문의 문장을 실행하게 되고 실행 문장이 끝나면 try - catch 전체를 빠져나가 다음 문장을 실행한다. 
      일치하는 catch  문이 없다면, 
      예외는 처리되지 못하고 프로그램은 비정상 종료하게 된다.

- try 문에서 예외가 발생하지 않는 경우

  
catch 문을 거치지 않고 try - catch 전체를 빠져나가 다음 문장을 실행한다.

 

catch 문에서 유용하게 활용할 수 있는 인스턴스 메서드가 두 가지 정도 있다.

printStackTrace()는 예외가 발생하기까지의 호출 스택 정보와

예외 메세지를 출력해줌으로써 예외에 대한 정보를 파악할 수 있게 도와준다. 

또 getMessage()는 printStackTrace() 기능에서 예외 메세지만을 출력하여 보여 준다.

이들을 적절히 활용하면 예외에 대한 발생 원인을 보다 쉽게 알 수 있게 된다.

 

 

4. 예외 처리하기

 

throw 키워드를 이용해서 고의적으로 예외를 발생 시킬 수도 있다.

이렇게 예외를 발생 시키면 checked Exception 의 경우에는 예외를 처리하기 위해 컴파일 에러를 발생 시킨다.

 

예외를 처리하는 방법은 크게 두 가지가 있다.

첫 번째는 앞서 말한 try - catch 문을 이용해서 그 자리에서 예외 처리하는 방법.

두 번째는 예외 처리를 해당 메서드를 호출하는 곳에서 처리하도록 책임 전가 시키는 방법이 있다.

 

책임 전가 시키는 방법은 키워드 throws 를 사용하여 명시적으로 나타낼 수 있다.

void method() throws Exception1, Exception2 ... {

}

이처럼 예외가 여러 개인 경우에는 구분자 , 를 사용하면 된다.

이러한 메서드는 호출부에서 예외 처리를 해야 함을 명시적으로 알려준다.

호출부에서는 try - catch 문을 통해 예외 처리 문을 작성할지 또는 또다른 호출부로 책임 전가할지를 결정하면 된다.

다만 예외 처리는 공통된 예외일수록 최초 호출자에 가깝게 처리하는 것이 좋다.

 

만약 예외 처리를 계속 전가하다 main 메서드에서까지 throws 를 하고 try - catch 를 통한 예외 처리를 하지 않았다면, 

main 메서드는 종료되어 프로그램이 종료되어 버리기 때문에 주의해야 한다.

 

 

5. finally 블럭

 

finally 블럭은 try - catch 문 아래에 쓰이며,

try - catch 문에서의 예외 발생 여부와 상관 없이 반드시 실행되어야 하는 코드를 작성하는 블럭이다.

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

finally가 있는 try - catch 문은 예외 발생 시에는 try - catch - finally 순으로 진행되고, 

예외가 발생하지 않으면 try - finally 순으로 진행된다.

 

class FinallyTest {
	public static void main(String[] args) {
    	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 블럭이 있는 경우에는 finally 블럭이 반드시 실행된다.

 

 

6. 사용자 정의 예외 만들기

 

기존의 정의된 예외뿐만 아니라 사용하고자 하는 특성에 맞춰 새롭게 예외에 대한 정의를 할 수 있다.

 

class MyException extends Exception {
	
    MyException(String msg) {
    	super(msg);
    }
}

Exception 뿐만 아니라 RuntimeException 을 상속 받아 구현할 수 있다.

예외를 선택적으로 처리하는 것이 가능하다면,

RuntimeException 으로 구현하는 것이 예외 처리 부담을 줄일 수 있는 방법이다.

 

또한 상속의 관계이기 때문에 Exception 또는 RuntimeException 의 메서드를 오버라이드 하여

더욱 사용자의 의도에 맞는 예외를 정의 가능하다.

'JAVA' 카테고리의 다른 글

Stack과 Queue  (0) 2022.09.29
Collection Framework : List, Set, Map  (0) 2022.09.28
내부 클래스와 익명 클래스  (0) 2022.09.26
인터페이스  (1) 2022.09.26
추상 클래스 (Abstract class)  (0) 2022.09.25