ksouth9

예외처리(exception handling)(2) 본문

Java

예외처리(exception handling)(2)

ksouth9 2022. 3. 18. 00:36

예외 발생시키기


키워드 'throw'를 사용해서 고의로 예외를 발생시킬 수 있다.

 

1. 연산자 new를 이용해서 발생시키려는 예외 클래스의 객체를 만든다.

- Exception e = new Exception("고의로 발생시켰음");

2. 키워드 throw를 이용해서 예외를 발생시킨다.

 - throw e;

public class ExceptionEx9 {

	public static void main(String[] args) {
		try {
			Exception e = new Exception("고의로 발생시킴.");
			throw e;	//예외를 발생시킴.
			//throw new Exception("고의로 발생시킴.);	위의 두 줄을 한 줄로 줄여 쓸 수 있다.
		} catch (Exception e) {
			System.out.println("에러 메시지 : "+e.getMessage());
			e.printStackTrace();
		}
		System.out.println("프로그램이 정상 종료되었음.");
	}

}//실행결과
java.lang.Exception: 고의로 발생시킴.
에러 메시지 : 고의로 발생시킴.
	at Test.ExceptionEx9.main(ExceptionEx9.java:7)
프로그램이 정상 종료되었음.

Exception인스턴스를 생성할 때, 생성자에 String을 넣어 주면, 이 String이 Exception인스턴스에 메시지로 저장된다. 이 메시지는 getMessage()를 이용해서 얻을 수 있다.

public class ExceptionEx10 {

	public static void main(String[] args) {
		throw new Exception();	//고의로 예외 발생.

	}

}//실행결과
Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
	Unhandled exception type Exception

	at Test.ExceptionEx10.main(ExceptionEx10.java:6)

이 예제를 작성한 후 컴파일 하면, 위와 같은 에러가 발생하며 컴파일이 완료되지 않는다. 예외처리가 되어야 할 부분에 예외처리가 되어 있지 않다는 에러이다. Exception클래스들(Exception클래스와 그 자손들)이 발생할 가능성이 있는 문장들에 대해 예외처리를 해주지 않으면 컴파일조차 되지 않는다. Exception클래스는 사용자의 실수와 같은 외적인 요인에 의해 발생하는 예외이다. 그러므로 반드시 예외 처리를 해주어야 한다.

 

public class ExceptionEx11 {

	public static void main(String[] args) {
		throw new RuntimeException();	//고의로 예외 발생.

	}

}//실행결과
Exception in thread "main" java.lang.RuntimeException
	at Test.ExceptionEx11.main(ExceptionEx11.java:6)

이 예제는 예외처리를 하지 않았음에도 불구하고 성공적으로 컴파일이 되었다. 그러나 실행하면, 위의 실행결과처럼 RuntimeException이 발생하여 비정상적으로 종료된다.

RuntimeException클래스와 그 자손(RuntimeException클래스들)에 해당하는 예외는 프로그래머에 의해 실수로 발생하는 것들이기 때문에 예외처리를 강제하지 않는 것이다. 

 

컴파일러가 예외처리를 확인하지 않는 RuntimeException클래스들은. 'unchecked예외'라고 부르고, 예외처리를 확인하는 Exception클래스들은. 'checked예외'라고 부른다.

 

RuntimeException클래스 - unchecked예외

Exception클래스 - checked예외

메서드에 예외 선언하기


메서드에 예외를 선언하려면, 메서드의 선언부에 키워드 'throws'를 사용해서 메서드 내에서 발생할 수 있는 예외를 적어주면 된다. 그리고 예외가 여러 개일 경우에는 쉼표(,)로 구분한다.

void method() throws Exception1, Exception2, ...Exception {
	//메서드 내용
}

※예외를 발생시키는 키워드는 'throw' , 메서드에서 선언할 때는 'throws'

 

만약 아래와 같이 모든 예외의 최고조상인 Exception클래스를 메서드에 선언하면, 이 메서드는 모든 종류의 예외가 발생할 가능성이 있다는 뜻이다.

void method() throws Exception {
	//메서드 내용
}

※이렇게 선언하면, 이 예외뿐만 아니라 그 자손타입의 예외까지도 발생할 수 있다.

 

자바에서는 메서드를 작성할 때 메서드 내에서 발생할 가능성이 있는 예외를 메서드의 선언부에 명시하여 이 메서드를 사용하는 쪽에서는 이에 대한 처리를 하도록 강요하기 때문에, 프로그래머들의 짐을 덜어 주고 보다 견고한 프로그램 코드를 작성할 수 있도록 도와준다.

 

Java API문서에서 찾아본 java.lang.Object클래스의 wait메서드의 선언부에는 InterruptedException이 키워드 throws와 함께 적혀있다. 이것이 의미하는 바는 이 메서드에서는 InterruptedException이 발생할 수 있으니, 이 메서드를 호출하고자 하는 메서드에서는 InterruptedException을 처리해주어야 한다는 것이다.

InterruptedException은 Exception클래스의 자손이다. 따라서 InterruptedException은 반드시 처리해주야 하는 예외이다. 그래서 wait메서드의 선언부에 키워드 throws와 함께 선언되어져 있는 것이다. 

 

또 다른 예로 IllegalMonitorStateException은 RuntimeException클래스의 자손이므로 IllegalMonitorStateException은 예외처리를 해주지 않아도 된다. 그래서 wait메서드의 선언부에 IllegalMonitorStateException을 적지 않은 것이다.

 

메서드에 예외를 선언할 때 일반적으로 RuntimeException클래스들은 적지 않는다. 이 들을 메서드 선언부의 throws에 선언한다고 해서 문제가 되지는 않지만, 보통 반드시 처리해주어야 하는 예외들만 선언한다. 

이처럼 Java API문서를 통해 사용하고자 하는 메서드의 선언부와 'Throws:'를 보고, 이 메서드에서는 어떤 예외가 발생할 수 있으며 반드시 처리해주어야 하는 예외는 어떤 것들이 있는지 확인하는 것이 좋다.

 

사실 예외를 메서드의 throws에 명시하는 것은 예외를 처리하는 것이 아니라, 자신(예외가 발생할 가능성이 있는 메서드)을 호출한 메서드에게 예외를 전달하여 예외처리를 떠맡기는 것이다.

 

이처럼 예외가 발생한 메서드에서 예외처리를 하지 않고 자신을 호출한 메서드에게 예외를 넘겨줄 수는 있지만, 이것으로 예외가 처리된 것은 아니고 예외를 단순히 전달만 하는 것이다. 결국 어느 한 곳에서는 반드시 try-catch문으로 예외처리를 해주어야 한다.

finally 블럭


finally 블럭은 예외의 발생여부에 상관없이 실행되어야할 코드를 포함시킬 목적으로 사용된다.

try-catch문의 끝에 선택적으로 덧붙여 사용할 수 있으며, try-catch-finally의 순서로 구성된다.

try {
	//예외가 발생할 가능성이 있는 문장들을 넣는다.
    } catch (Exception e) {
    	//예외처리를 위한 문장을 적는다.
    } finally {
    	//예외의 발생여부에 관계없이 항상 수행되어야하는 문장들을 넣는다.
        //finally블럭은 try-catch문의 맨 마지막에 위치해야한다.
    }

예외가 발생한 경우 - 'try-catch-finally'순으로 실행

예외가 발생하지 않은 경우 - 'try-finally'순으로 실행

 

public class FinallyTest {

	public static void main(String[] args) {
		try {
			startInstall();		//프로그램 설치에 필요한 준비를 한다.
			copyFiles();		//파일들을 복사한다.
			deleteTempFiles();	//프로그램 설치에 사용된 임시파일들을 삭제한다.
		} catch (Exception e) {
			e.printStackTrace();
			deleteTempFiles();	//프로그램 설치에 사용된 임시파일들을 삭제한다.
		}

	}

	static void startInstall() {
		//프로그램 설치에 필요한 준비를 하는 코드를 적는다.
	}
	static void copyFiles() {
		//파일들을 복사하는 코드를 적는다.
	}
	static void deleteTempFiles() {
		//임시파일들을 삭제하는 코드를 적는다.
	}
}

이 예제가 하는 일은 프로그램설치를 위한 준비를 하고 파일들을 복사하고 설치가 완료되면, 프로그램을 설치하는데 사용된 임시파일들을 삭제하는 순서로 진행된다.

프로그램의 설치과정 중에 예외가 발생하더라도, 설치에 사용된 임시파일들이 삭제되도록 catch블럭에 deleteTempFiles()메서드를 넣었다. 결국 try블럭의 문장을 수행하는 동안에(프로그램을 설치하는 과정에), 예외의 발생여부에 관계없이 deleteTempFiles()메서드는 실행되어야 하는 것이다. 이럴 때 finally블럭을 사용하면 좋다. 

public class FinallyTest2 {

	public static void main(String[] args) {
		try {
			startInstall();		//프로그램 설치에 필요한 준비를 한다.
			copyFiles();		//파일들을 복사한다.
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			deleteTempFiles();	//프로그램 설치에 사용된 임시파일들을 삭제한다.
		}

	}
	static void startInstall() {
		//프로그램 설치에 필요한 준비를 하는 코드를 적는다.
	}
	static void copyFiles() {
		//파일들을 복사하는 코드를 적는다.
	}
	static void deleteTempFiles() {
		//임시파일들을 삭제하는 코드를 적는다.
	}
}

deleteTempFiles()메서드를 finally블럭에 넣어서 예외 발생여부에 상관없이 실행된다. 그리고 코드의 양을 줄였다.

public class FinallyTest3 {

	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블럭의 문장들은 수행된다.