본문 바로가기
Java

[Java] 20. 예외 처리(2)

by byeongoo 2019. 4. 18.

1. 두개 이상의 catch

try-catch 문을 쓸 때 catch에 Exception e라고 아무 생각 없이 썼습니다. 이 catch 블록이 시작되기 전에 있는 소괄호에는 예외의 종류를 명시합니다. 다시 말해서, 항상 Exception e라고 쓰는 것은 아니라는 것입니다. 전 글에서 사용했던 예제를 활용하여 다음과 같은 클래스를 만들어 봅시다.

 

package c.exception;

public class ExceptionSample {

    public static void main(String[] args) {
        ExceptionSample sample = new ExceptionSample();
        sample.multiCatch();
    }

    public void multiCatch() {
    	int[] intArray = new int[5];
    	try {
    		System.out.println(intArray[5]);
    	} catch(ArrayIndexOutOfBoundsException e) {
    		System.out.println("ArrayIndexOutOfBoundsException occured");
    	}catch(Exception e) {
    		System.out.println("Exception occured");
    	}
    } 
}

이렇게 try는 한 번만 쓰고, catch 블록을 여러개 만들어도 문제 없습니다. 실행을 해본다면 다음과 같은 결과가 출력 될 것 입니다.

 

ArrayIndexOutOfBoundsException occured

 

앞에 있는 catch에 있는 것만 처리되는구나 라고 생각할 수도 있습니다. 어떻게 보면 맞는 말인데 틀린 말이기도 합니다. catch 블록의 순서는 매우 중요합니다. catch 블록은 순서를 따집니다. catch 블록의 순서를 다음과 같이 거꾸로 바꿔봅시다. 

 

package c.exception;

public class ExceptionSample {

    public static void main(String[] args) {
        ExceptionSample sample = new ExceptionSample();
        sample.multiCatch();
    }

    public void multiCatch() {
    	int[] intArray = new int[5];
    	try {
    		System.out.println(intArray[5]);
    	} catch(Exception e) {
    		System.out.println("Exception occured");
    	} catch(ArrayIndexOutOfBoundsException e) {
    		System.out.println("ArrayIndexOutOfBoundsException occured");
    	}
    } 
}

이렇게 해놓고 컴파일하면 다음과 같이 에러 메시지가 출력됩니다.

Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
	Unreachable catch block for ArrayIndexOutOfBoundsException. It is already handled by the catch block for Exception

	at c.exception.ExceptionSample.multiCatch(ExceptionSample.java:16)
	at c.exception.ExceptionSample.main(ExceptionSample.java:7)

뭘 이미 잡았다는 거야? 순서만 바꿨는데 라고 생각되겠지만 Exception의 비밀에 대해서 알아봅시다. 모든 객체의 부모 클래스는 바로 java.lang.Object 클래스입니다. 그렇다면 모든 예외의 부모 클래스는 무엇일까요? 바로 java.lang.Exception 클래스 입니다. Exception 클래스는 java.lang 패키지에 선언되어 있기 때문에 별도로 import 할 필요는 없었습니다.

 

예외는 부모 예외 클래스가 이미 catch를 하고, 자식 클래스가 그 아래에서 catch를 하도록 되어 있을 경우에는 자식 클래스가 예외를 처리할 기회가 없습니다. Exception 클래스가 모든 클래스의 부모 클래스이고, 배열에서 발생시키는 ArrayIndexOutOfBoundsException은 Exception 클래스의 자식 클래스이기 때문에 절대로 Exception 클래스로 처리한 catch 블록 이후에 선언한 블록은 처리될 일이 없기 때문에 왜 필요 없는 것을 만드냐고 컴파일러가 에러를 던집니다. 다음과 같이 메소드를 수정합시다.

 

package c.exception;

public class ExceptionSample {

    public static void main(String[] args) {
        ExceptionSample sample = new ExceptionSample();
        sample.multiCatch();
    }

    public void multiCatch() {
    	int[] intArray = new int[5];
    	try {
    		System.out.println(intArray[5]);
    	} catch(NullPointerException e) {
    		System.out.println("NullPointerException occured");
    	} catch(ArrayIndexOutOfBoundsException e) {
    		System.out.println("ArrayIndexOutOfBoundsException occured");
    	} catch(Exception e) {
    		System.out.println("Exception occured");
    	}
    } 
}

 

NullPointerException이라는 것을 추가했습니다. 여기서는 NullPointerException이 발생하지 않습니다. 현재 예제상으로는 null인 객체를 갖고 우리가 어떤 작업을 하지 않기 때문에 NullPointerException으로 선언한 블록은 그냥 건너 뛰고 그 다음에 있는 catch 문장을 처리하는 것 입니다.

 

그리고, 여러분들이 catch 문을 사용할 때에는 지금과 같이 Exception 클래스로 catch하는 것을 가장 아래에 추가할 것을 권장합니다. 예상치 못한 예외가 발생하면 제대로 처리가 되지 않기 때문입니다. 따라서, 가장 마지막 catch 블록은 Exception 클래스를 선언하여 예외들이 빠져 나가지 않게 꽁꽁 묶어 주는 것이 좋습니다.

 

배운 내용을 정리를 하면 다음과 같습니다.

 

● try 다음에 오는 catch 블록은 1개 이상 올 수 있다.

 

● 먼저 선언한 catch 블록의 예외 클래스가 다음에 선언한 catch 블록의 부모에 속하면, 자식에 속하는 catch 블록은 절대 실행될 일이 없으므로 컴파일이 되지 않습니다.

 

● 하나의 try 블록에서 예외가 발생하면 그 예외가 관련이 있는 catch 블록을 찾아서 실행한다.

 

● catch 블록 중 발생한 예외와 관련있는 블록이 없으면, 예외가 발생되면서 해당 쓰레드가 끝난다. 따라서 마지막 catch 블록에는 Exception 클래스로 묶어주는 버릇을 들여 놓아야 안전한 프로그램이 될 수 있다.

 

2. 예외의 3가지 종류

자바에는 세 종류의 예외가 존재합니다.

 

● checked exception

 

● error

 

● runtime exception 혹은 unchecked exception

 

두 번째와 세 번째에 있는 error와 unchecked exception을 제외한 모든 예외는 checked exception 입니다. 그렇다면 두 번째와 세 번째 예외가 어떤것인지만 안다면 예외의 종류에 대한 이해는 확실히 될 것입니다.

 

먼저 error는 자바 프로그램 밖에서 발생한 예외를 말합니다. 가장 흔한 예가 서버의 디스크가 고장 났다든지, 메인보드가 고장나 자바 프로그램이 제대로 동작하지 못하는 경우가 속합니다. Exception 클래스는 에러가 아닙니다. 자바 프로그램에 오류가 발생했을 때, 오류의 이름이 Error로 끝나면 에러이고, Exception으로 끝나면 예외 입니다. 

 

에러와 예외의 가장 큰 차이는 프로그램 안에서 발생했는지, 밖에서 발생했는지 여부입니다. 하지만 더 큰 차이는 프로그램이 멈추어 버리느냐, 계속 실행할 수 있느냐의 차이입니다. Error는 프로세스에 영향을 주고, Exception은 쓰레드에만 영향을 줍니다.

 

런타임 예외는 예외가 발생할 것을 미리 감지하지 못했을 때 발생합니다. 이 런타임 예외에 해당하는 모든 예외들은 RuntimeException의 확장한 예외들입니다. 이 예외를 묶어주지 않는다고 해서 컴파일할 때 예외가 발생하지 않습니다. 하지만 실행시에는 발생할 가능성이 있습니다. 그래서, 이러한 예외들을 런타임 예외라고 합니다. 그리고 컴파일시에 체크를 하지 않기 때문에 지금까지 설명한 내용을 바탕으로 예외들의 상관 관계를 그림으로 나타내면 다음과 같습니다. 

 

Checked Exception의 경우에는 개발자의 의도와는 상관없이 예외가 발생할 경우입니다. 반드시 예외를 처리하는 코드를 작성해주어야합니다. 그렇지 않으면 컴파일 에러가 납니다.

 

Unchecked Exception(Runtime Exception)의 경우에는 개발자의 부주의로 인한 예외입니다. 따라서 catch나 throws를 이용해서 처리하지 않아도 됩니다.

3. 모든 예외의 할아버지는 java.lang.Throwable 클래스

앞에서 살펴본 Exception과 Error의 공통 부모 클래스는 Object 클래스입니다. 그리고, 공통 부모 클래스가 또 하나 있는데, 바로 java.lang 패키지에 선언된 Throwable 클래스입니다. 그래서 Exception이나 Error를 처리할 때 Throwable로 처리해도 무관합니다. 상속 관계가 이렇게 되어 있는 이유는 Exception이나 Error의 성격은 다르지만 모두 동일한 이름의 메소드를 사용하여 처리할 수 있도록 하기 위함입니다. 그러면 가장 먼저 Throwable에 어떤 생성자가 선언되어 있는지 살펴 봅시다.

 

· Throwable()

· Throwable(String message)

· Throwable(String message, Throwable cause)

· Throwable(Throwable cause)

 

아무런 매개 변수가 없는 생성자는 기본적으로 제공합니다. 그리고, 예외 메시지를 String으로 넘겨줄 수도 있습니다. 그리고, 별도로 예외의 원일을 넘길 수 있도록 Throwable 객체를 매개 변수로 넘겨 줄 수도 있습니다. 생성자들의 목록을 보면 별로 대단한 것은 없습니다. Throwable 클래스에 선언되어 있고, Exception 클래스에서 Overriding하는 메소드는 10개가 넘습니다. 그 중에서 가장 많이 사용되는 몇몇 메소드를 살펴 봅시다. 

 

● getMessage()

 - 예외 메시지를 String 형태로 제공 받는다. 예외가 출력되었을 때 어떤 예외가 발생되었는지를 확인할 때 매우 유용하다. 이 메시지를 활용하여 별도의 예외 메시지를 사용자에게 보여주려고 할 때 좋다.

 

● toString()

· 예외 메시지를 String 형태로 제공 받는다. 그런데, getMessage() 메소드보다는 약간 더 자세하게, 예외 클래스 이름도 같이 제공한다.

 

● printStackTrace()

 - 가장 첫 줄에는 예외 메시지를 출력하고, 두 번째 줄부터는 예외가 발생하게 된 메소드들의 호출 관계를 출력해준다.

 

예제를 통해서 Throwable에 대해서 알아 봅시다.

 

package c.exception;

public class ExceptionSample {

    public static void main(String[] args) {
        ExceptionSample sample = new ExceptionSample();
        sample.throwable();
    }

    public void throwable() {
    	int[] intArray = new int[5];
    	try {
    		intArray = null;
    		System.out.println(intArray[5]);
    	} catch(Throwable t) {
    		System.out.println(t.getMessage());
    		System.out.println(t.toString());
    		t.printStackTrace();
    	}
    }
}

 

null
java.lang.NullPointerException
java.lang.NullPointerException
	at c.exception.ExceptionSample.throwable(ExceptionSample.java:14)
	at c.exception.ExceptionSample.main(ExceptionSample.java:7)

getMessage()보다는 toString() 메소드가 더 자세하고, printStackTrace() 메소드가 더 자세합니다. 이렇게 printStackTrace() 메소드를 사용하면 매우 자세한 메시지를 볼 수 있습니다. 하지만 이 메소드는 개발할 때에만 사용하기 바랍니다. 여러분들이 운영할 시스템에 이 메소드를 사용하면 엄청나게 많은 양의 로그가 쌓일 것입니다.

'Java' 카테고리의 다른 글

[Java] 21. 어노테이션  (0) 2019.04.20
[Java] 20. 예외 처리(3)  (0) 2019.04.18
[Java] 20. 예외 처리(1)  (0) 2019.04.17
[Java] 19. 중첩 클래스(Nested Class)  (0) 2019.04.11
[Java] 18. enum 클래스  (0) 2019.04.09