예외 발생시키기
키워드 throw를 사용해서 프로그래머가 고의로 예외를 발생시킬 수 있으며, 방법은 아래의 순서를 따르면 된다.
- 먼저, 연산자 new를 이용해서 발생시키려는 예외 클래스의 객체를 만든다음
- Exception e =new Exception("고의로 발생시켰음");
- 키워드 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 ExceptionEx9.main(ExceptionEx9.java:6)
프로그램이 정상 종료되었음
Exception 인스턴스를 생성할 떄, 생성자에 String을 넣어주면, 이 String이 Exception 인스턴스에 메시지로 저장된다. 이 메시지는 getMessage()를 이용해서 얻을 수 있다.
public class ExceptionEx10 {
public static void main(String[] args) {
throw new Exception(); //Exception을 고의로 발생시킨다.
}
}
위 에제를 작성한 후에 컴파일 하면, 에러가 발생하며 컴파일이 완료되지 않는다. 예외처리가 되어야 할 부분에 예외처리가 되어 있지 않다는 에러이다. 위의 결과에서 알 수 있는 것처럼, 앞서 분류한 Exception클래스들(Exception클래스와 그 자손들)이 발생할 가능성이 있는 문장들에 대해 예외처리를 해주지 않으면 컴파일 조차 되지 않는다.
public class ExceptionEx11 {
public static void main(String[] args) {
throw new RuntimeException(); //RuntimeException을 고의로 발생시킨다.
}
}
위 예제는 예외처리를 하지 않았음에도 불구하고 이전의 예제와는 달리 성공적으로 컴파일 될 것이다. 그러나 실행하면, 위의 실행결과처럼 RuntimeException이 발생하여 비정상적으로 종료될 것이다.
이 예제가 명백히 RuntimeException을 발생시키는 코드를 가지고 있고, 이에 대한 예외처리를 하지 않았음에도 불구하고 성공적으로 컴파일 되었다.
이 장의 앞부분에 설명한 것과 같이 RuntimeException클래스와 그 자손들(RuntimeException클래스들)에 해당하는 예외는 프로그래머에 의해 실수로 발생하는 것들이기 때문에 예외처리를 강제하지 않는 것이다. 만일 RuntimeException클래슫르에 속하는 예외가 발생할 가능성이 있는코드에도 예외처리를 필수로 해야한다면, 아래와 같이 참조변수와, 배열이 사용되는 모든 곳에 예외처리를 해주어야 한다.
try{
int [] arr = new int[10]
System.out.println(arr[0]);
}catch (ArrayIndexOutOfBoundsException ae){
....
}catch (NUllPointerException ne){
...
}
컴파일러가 예외처리를 확인하지 않는 RuntimeException클래스들은 'unchecked예외'라고 부르고, 예외처리를 확인하는 Exception클래스들은 'checked예외'라고 부른다.
메서드에 예외 선언하기
예외를 처리하는 방법에는 지금까지 배워온 try-catch문을 사용하는 것 외에, 예외를 메서드에 선언하는 방법이 있다.
메서드에 예외를 선언하려면, 메서드의 선언부에 키워드 throws를 사용해서 메서드내에서 발생할 수 있는 예외를 적어주기만 하면된다. 그리고 예외가 여러 개일 경우에는 쉼표(,)로 구분한다.
void method() throws Exception1, Exception2, ... {
//메서드의 내용
}
- 예외를 발생시키는 키워드 throw와 예외를 메서드에 선언할 때 쓰이는 throws를 잘 구별해야 한다.
만일 아래와 같이 모든 예외의 최고조상인 Exception클래스를 메서드에 선언하면, 이 메서드는 모든 종료의 예외가 발생할 가능성이 있다는 뜻이다.
void method() thorw Exception{
//메서드의 내용
}
메서드의 선언부에 예외를 선언함으로써 메서드를 사용하려는 사람이 메서드의 선언부를 보았을 때, 이 메서드를 사용하기 위해서는 어떠한 예외들이 처리되어야 하는지 쉽게 알 수 있다.
기존의 많은 언어들에서는 메서드에 예외선언을 하지 않기 떄문에, 경험 많은 프로그래머가 아니고서는 어떤 상황에 어떤 종류의 예외가 발생할 가능성이 있는지 충분히 예측하기가 힘들기 때문에 그에 대한 대비를 하는 것이 어려웠다.
그러나 자바에서는 메서드를 작성할 때 메서드 내에서 발생할 가능성이 있는 예외를 메서드의 선언부에 명시하여 이 메서드를 사용하는 쪽에서는 이에 대한 처리를 하도록 강요하기 댸문에, 프로그래머들의 짐을 덜어주는 것은 물론이고 보다 견고한 프로그램 코들르 작성할 수 있게 도와준다.
Java API문서에 있는 java.lang.Object 클래스의 wait메서드에 대한 설명중, 메서드의 선언부에 InterruptedException이 키워드 throws와 함께 적혀있는 것을 볼 수 있다. 이것이 의미하는 바는 이 메서드에서는 InterruptedException이 발생할 수 있으니, 이 메서드를 호추랗고자 하는 메서드에는 InterruptedException을 처리해주어야 한다는 것이다.
InterruptedException은 Exception클래스의 자손임을 알 수 있다. 따라서 InterruptedException은 반드시 처리해주어야 하는 예외임을 알 수 있따. 그래서 wait메서드의 선언부에 키워드 throws와 함께 선언되어져 있는 것이다.
Java API의 wait메서드의 설명의 아래쪽에 있는 Throws.를 보면 wait메서드에서 발생할 수 있는 예외의 리스트와 언제 발생하는지에 대한 설명이 덧붙여져 있다.
여기에는 두 개의 예외가 적혀져 있는데 메서드에 선언되어 있는 InterruptedException 외에 또 하나의 예외 (IllegalMonitorStateException)가 있다.
IllegalMonitorStateException 역시 링크가 걸려있는데, 클릭하면 IllegalMonitorStateException에 대한 정보를 얻을 수 있다.
IllegalMonitorStateException은 RuntimeException클래스의 자손이므로 IllegalMonitorStateException은 예외처리를 해주지 않아도 된다.
그래서 wait메서드의 선언부에 IllegalMonitorStateException를 적지 않은 것이다.
지금까지 알아본 것처럼 메서드를 예외를 선언할 때 일반적으로 RuntimeException클래스들은 적지 않는다. 이 들을 메서드의 선언부의 throws에 선언한다고 해서 문제가 되지 않지만, 보통 반드시 처리해주어야 하는 예외들만 선언한다.
이처럼 Java API문서를 통해 사용하고자 하는 메서드의 선언부와 Thorws:를 보고, 이 메서드에서는 어떤 예외가 발생할 수 있으며 반드시 처리해주어야 하는 예외는 어떤 것들이 있는지 확인하는 것이 좋다.
사실 예외를 메서드의 throws에 명시하는 것은 예외를 처리하는 것이 아니라, 자신(예외가 발생할 가능성이 있는 메서드)을 호출한 메서드에게 예외를 전달하여 예외처리를 떠맡기는 것이다.
예외를 전달받은 메서드가 또 다시 자신을 호출한 메서드에게 전달할 수 있으며, 이런식으로 계속 호출스택에 있는 메서드들을 따라 전달되다가 제일 마지막에 있는 main메서드에서도 예외가 처리되지 않으면, main메서드마저 종료되어 프로그램이 전체가 종료된다.
public class ExceptionEx12 {
public static void main(String[] args) throws Exception{
method1(); //같은 클래스내의 static멤버이므로 객체 생성없이 직접 호출 가능
}
static void method1() throws Exception{
method2();
}
static void method2() throws Exception{
throw new Exception();
}
}
====================
Exception in thread "main" java.lang.Exception
at ExceptionEx12.method2(ExceptionEx12.java:13)
at ExceptionEx12.method1(ExceptionEx12.java:9)
at ExceptionEx12.main(ExceptionEx12.java:5)
위의 실행결과를 보면 ,프로그램의 실행 도중 java.lang.Exeption이 발생하여 비정상적으로 종료했다는 것과 예외가 발생했을 때 호출 스택(call stack)의 내용을 알 수 있다.
위의 결과로부터 다음과 같은 사실을 알 수 있다.
- 예외가 발생했을 때, 모두 3개의 메서드(main, method1, method2)가 호출스택에 있었으며,
- 예외가 발생한 곳은 제일 윗줄에 있는 method2()라는 것과
- main메서드가 method1()을, 그리고 method1()은 method2()를 호출했다는 것을 알 수 있다.
위의 예제를 보면, method2()에서 'throw new Exception(); 문장에 의해 예외가 강제적으로 발생했으나 try-catch문으로 예외처리를 해주지 않았으므로, method2()는 종료되면서 예외를 자신을 호출한 method21()에게 넘겨준다. method1()에서도 역시 예외처리를 해주지 않았으므로 종료되면서 main메서드에게 예외를 넘겨준다.
그러나 main메서드에서 조차 예외처리를 해주지 않았으므로 main메서드가 종료되어 프로그램이 예외로 인해 비정상적으로 종료되는 것이다.
이처럼 예외가 발생한 메서드에서 예외처리를 하지 않고 자신을 호출한 메서드에게 예외를 넘겨줄 수는 있지만, 이것으로 예외가 처리된 것은 아니고 예외를 단순히 전달만 하는 것이다. 결국 어느 한 곳에서는 반드시 try-catch문으로 예외처리를 해주어야 한다.
public class ExceptionEx13 {
public static void main(String[] args) {
method1();
}
static void method1(){
try{
throw new Exception();
}catch(Exception e){
System.out.println("method1 메서드에서 예외가 처리되었습니다.");
e.printStackTrace();
}
}
}
====================
method1 메서드에서 예외가 처리되었습니다.
java.lang.Exception
at ExceptionEx13.method1(ExceptionEx13.java:10)
at ExceptionEx13.main(ExceptionEx13.java:5)
예외는 처리되었지만, printStackTrace()를 통해 예외에 대한 정보를 화면에 출력하였다. 예외가 method1()에서 발생했으며, main메서드가 method1()을 호출했음을 알 수 있다.
public class ExceptionEx14 {
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 ExceptionEx14.method1(ExceptionEx14.java:13)
at ExceptionEx14.main(ExceptionEx14.java:6)
두 예제 모두 main 메서드가 method1()을 호출하며, method1()에서 예외가 발생한다. 차이점은 예외처리 방법에 있다.
예제 13은 method1()에서 예외처리를 했고, 예제14는 method1()에서 예외를 선언하며 자신을 호출하는 메서드(main 메서드)에 예외를 전달했으며, 호출한 메서드(main 메서드)에서는 try-catch문으로 예외처리를 했다.
예제13처럼 예외가 발생한 메서드(method1)내에서 처리되어지면, 호출한 메서드(main 메서드)에서는 예외가 발생했다는 사실조차 모르게 된다.
예제14처럼 예외가 발생한 메서드에서 예외를 처리하지 않고 호출한 메서드를 넘겨주면, 호출한 메서드에서는 method1()을 호출한 라인에서의 예외가 발생한 것으로 간주되어 이에 대한 처리를 하게 된다.
이처럼 예외가 발생한 메서드 method1()에서 예외를 처리할 수도 있고, 예외가 발생한 메서드를 호출한 main 메서드에서 처리할 수도 있다. 또는 두 메서드가 예외처리를 분담할 수 있다.
import java.io.*;
public class ExceptionEx15 {
public static void main(String[] args) {
//comman line에서 입력 받은 값을 이름을 갖는 파일을 생성한다.
File f = createFile(args[0]);
System.out.println(f.getName() + " 파일이 성공적으로 생성되었습니다.");
}
static File createFile(String fileName){
try{
if(fileName==null || fileName.equals(""))
throw new Exception("파일이름이 유효하지 않습니다.");
}catch(Exception e){
//fileName이 부적절한 경우, 파일 이름을 '제목없음.txt'로 한다.
fileName = "제목없음.txt";
}finally{
File f = new File(fileName);//File클래스의 객체를 만든다.
createNewFile(f); //생성된 객체를 이용해서 파일을 생성한다.
return f;
}
}
static void createNewFile(File f){
try{
f.createNewFile(); //파일을 생성한다.
}catch(Exception e){ }
}
}
- 실행 시 커맨드 라인에 파일이름을 입력하지 않으면, args[0]이 유효하지 않으므로 File f= createFile (args[0]);에서 ArrayIndexOuntBoundsException이 발생한다
이 예제는 예외가 발생한 메서드에서 직접 예외를 처리하도록 되어 있다. createFile메서드를 호출한 main메서드에서는 예외가 발생한 사실을 알지 못한다. 적절하지 못한 파일이름(fileName)이 입력되면, 예외를 발생시키고, catch블럭에서, 파일이름을 제목없음.txt로 설정해서 파일을 생성한다. 그리고 finally블럭에서는 예외의 발생여부에 관계없이 파일을 생성하는 일을 한다.
import java.io.File;
public class ExceptionEx16 {
public static void main(String[] args) {
try{
File f = createFile(args[0]);
System.out.println(f.getName()+"파일이 성공적으로 생성되었습니다.");
}catch(Exception e){
System.out.println(e.getMessage()+" 다시 입력해 주시기 바랍니다.");
}
}
static File createFile(String fileName) throws Exception{
if(fileName == null || fileName.equals(""))
throw new Exception("파일 이름이 유효하지 않습니다.");
File f = new File(fileName); //File 클래스의 객체를 만든다.
//File 객체의 createNewFile메서드를 이용해서 실제 파일을 생성한다.
f.createNewFile();
return f; //생성된 객체의 참조를 변환한다.
}
}
이 예제에서는 예외가 발생한 createFile메서드에서 잘못 입력된 파일이름을 임의로 처리하지 않고, 호출한 main메서드에게 예외가 발생했음을 알려서 파일이름을 다시 입력받도록 하였다.
예제 15와 달리 createFile메서드에 예외를 선언했기 때문에, File클래스의 createNewFile()에 대한 예외처리를 하지 않아도 되므로 createNewFile(File f)메서드를 굳이 따로 만들지 않았다. 두 예제 모두 커맨드라인으로부터 파일이름을 입력받아서 파일을 생성하는 일을 하며, 파일 이름을 잘못 입력했을 때(null 또는 빈 문자열일 때) 예외가 발생하도록 되어있다.
차이점은 예외의 처리방법에 있다. 예제 8-15는 예외가 발생한 createFile메서드 자체내에서 처리를 하며, 예제 16은 createFile메서드를 호출한 메서드(main 메서드)에서 처리를 한다.
이처럼 예외가 발생한 메서드 내에서 자체적으로 처리해도 되는 것은 메서드 내에서 try-catch문을 사용해서 처리하고, 두번째 예제처럼 메서드에 호출 시 넘겨받아야 할 값(fileName)을 다시 받아야 하는 경우(메서드 내에서 자체적으로 해결이 안되는 경우)에는 예외를 메서드에 선언해서, 호출한 메서드에서 처리해야 한다.