프로그램이 실행 중 어떤 원인에 의해서 오작동을 하거나, 비정상적으로 종료되는 경우가 있다.이러한 결과를 초래하는 원인을 프로그램 에러 또는 오류라고 한다. 이를 발생시점에 따라 "컴파일 에러(compile-time-error)"와 "런타임 에러(runtime error)" 이외에 논리적 에러가 있다.
자바에서는 실행 시(runtime)에 발생할 수 있는 프로그램 "오류"를
'에러(error)'와 '예외(exception)', 두가지로 구분한다.
에러는 메모리 부족(OutOfMemoryError)이나 스택오버플로우(StackOverflowError)와 같이 일단 발생하면 복구할 수 없는 심각한 오류이고, 예외는 발생하더라도 수습될 수 있는 비교적 덜 심각한 것이다.
모든 클래스의 조상은 Object이므로 Exception,Error 클래스 역시 똑같다.
모든 예외의 최고 조상은 Exception클래스이다.
예외 클래스들은 Exception 클래스와 자손들, RuntimeException과 자손들로 나눌 수 있다.
에러는 어쩔 수 없지만, 예외는 프로그래머가 이에 대한 처리를 미리 해주어야 한다. 예외처리(exception handling)이란, 프로그램 실행 시 발생할 수 있는 예끼치 못한 예외의 발생에 대비한 코드를 작성하는 것이며, 예외처리의 목적은 예외의 발생으로 인한 실행 중인 프로그램의 갑작스런 비정상 종료를 막고, 정상적인 실행상태를 유지할 수 있도록 하는 것이다.
이외로 발생한 예외를 처리하지 못하면, 프로그램은 비정상적으로 종료되며, 처리되지 못한 예외(uncaugth exception)은 JVM(UncaughtExceptionHandler)의 예외처리기가 받아서 예외의 원인을 화면에 출력한다.
try {
// 예외가 발생할 가능성이 있는 문장들을 넣는다.
} catch (Exception1 e1) {
// Exception1이 발생했을 경우, 이를 처리하기 위한 문장을 적는다.
} catch (Exception2 e2) {
// Exception2가 발생했을 경우, 이를 처리하기 위한 문장을 적는다.
} catch (Exception3 e3) {
// Exception3가 발생했을 경우, 이를 처리하기 위한 문장을 적는다.
}
다중 try-catch를 사용할 때를 제외하고는, catch블럭의 괄호 내에 선언된 변수는 catch블럭 내에서만 유효하기 때문에, 위의 모든 catch블럭에 참조변수 'e'하나 만을 사용해도 된다.
예외의 발생과 catch블럭
예외가 발생한 문장이 try블럭에 포함되어 있다면, 이 예외를 처리 할 수 있는 catch 블럭이 있는지 찾게 된다.
class Exception1 {
public static void main(String args[]) {
System.out.println(1);
System.out.println(2);
try {
System.out.println(3);
System.out.println(0/0);
// 일부러 예외를 발생시킨 것이며,
// 0으로 나눠서 ArithmeticException이 발생한다.
System.out.println(4); // 실행되지 않는다.
} catch (Exception e) { // Exception이 조상 클래스 이기 떄문에 어떤 종류의 예외가 발생하더라도 처리 된다.
System.out.println(5);
}// try-catch의 끝
System.out.println(6);
}// main메서드의 끝
}
멀티 catch블럭
//일반적인 try-catch
try {
생략
} catch (ExceptionA e) {
e.printStackTrace();
{ catch (ExceptionB e) {
e2.printStackTrace();
}
------------------------
//멀티 catch
try {
생략
} catch (ExceptionA | ExceptionB e) {
e.printStackTrace();
} // 두 예외 클래스가 조상과 자손의 관계에 있다면, 하나만 써주는것이 옳다.
멀티 catch는 하나의 catch블럭으로 여러 예외를 처리하는 것이기 때문에, 발생한 예외를 멀티 catch블럭으로 처리하게 되었을 때, 멀티 catch블럭 내에서는 실제로 어떤예외가 발생한 것인지 알 수 없다. 그래서 참조변수 e로 멀티 catch블럭에 '|'기호로 연결된 예외 클래스들의 공통 분모인 조상 예외 클래스에 선언된 멤버만 사용할 수 있다.
try {
생략
} catch (ExceptionA | ExceptionB e) {
e.methodA(); // 에러,ExceptionA에 선언된 methodA ()는 호출불가
if(e instanceof ExceptionA) {
ExceptionA e1 = (ExceptionA)e;
e1.methodA(); // 가능, ExceptionA에 선언된 메서드 호출가능
} else { // if(e instanceof ExceptionB)
생략
}
e.printStackTrace();
}
키워드 throw를 사용해서 프로그래머가 고의로 예외를 발생시킬 수 있다.
Exception e = new Exception("고의로 발생시켰음");throw e; 예시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();
}
System.out.println("프로그램 정상 종료");
}
}finally블럭은 예외의 발생여부에 상관없이 실행되어야 할 코드를 포함시킬 목적으로 사용한다. try-catch문의 끝에 선택적으로 덧붙여 사용 가능하며, try-catch-finally 순서로 구성된다.
try {
// 예외가 발생할 가능성이 있는 문장들을 넣는다.
} catch (Exception e1) {
// 예외처리를 위한 문장을 적는다.
} finally {
// 예외의 발생여부에 관계없이 항상 수행되어야하는 문장들을 넣는다.
// finally블럭은 try-catch문의 맨 마지막에 위치해야한다.
예외가 발생한 경우 try -> catch -> finally 순으로,
예외가 발생하지 않은 경우 try -> finally 순으로 실행된다.
class Finallytest {
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() { 생략 }
}
JDK1.7부터 try-with-resources문이라는 try-catch문의 변형이 새로 추가되었다. 이 구문은 주로 I/O와 관련된 클래스를 사용할 때 유용하다.
주로 "입출력에 사용되는 클래스 중에서는 사용한 후에 꼭 닫아줘야 하는것들"이 있다.
try {
fis = new FileInputStream("score.dat");
dis = new DataInputStream(fis);
} catch (IOException ie) {
ie.printStackTrace();
} finally {
try {
if(dis!=null)
dis.close();
} catch(IOException ie) {
ie.printStackTrace();
}
}
finally 블럭안에 try-catch문을 추가해서 close()에서 발생할 수 있는 예외를 처리하도록 변경했는데,코드가 복잡해져서 별로 보기에 좋지 않다. 더 안좋은 것은 try 블럭과 finally블럭에서 모두 예외가 발생하면, try블럭의 예외는 무시된다는 것이다.
try-with-resources로 변환
// 괄호 ()안에 두 문장 이상 넣을 경우 ';'로 구분한다.
try (FileInputStream fis = new FileInputStream("score.dat");
DataInputStream dis = new DataInputStream(fis)) {
while(true) {
score = dis.readInt();
System.out.println(score);
sum+=score;
}
} catch (EOFException e) {
System.out.println("점수의 합은" + sum + "입니다.");
} catch (IOException ie) {
ie.printStackTrace();
}
try-with-resource문의 괄호()안에 객체를 생성하는 문장을 넣으면, 이 객체는 따로 close()를 호출하지 않아도 try블럭을 벗어나는 순간 자동적으로 close()가 호출된다.그런 다음 catch블럭 또는 finally블럭이 수행된다.
기존 정의된 예외 클래스 외에 필요에 따라 프로그래머가 새로운 예외 클래스를 정의하여 사용할 수 있다. Exception클래스 또는 RuntimeException클래스로 부터 상속받아 클래스를 만들지만, 필요에 따라서 알맞은 예외 클래스를 선택할 수 있다.
class MyException extends Exception {
MyException(String msg) { // 문자열을 매개변수로 받는 생성자
super(msg); // 조상인 Exception클래스의 생성자를 호출한다.
}
}
예외를 처리한 후에 인위적으로 다시 발생시키는 방법을 통해서 가능한데, 이것을 "예외 되던지기" 라고한다.
예외가 발생할 가능성이 있는 메서드에서 try-catch문을 사용해 예외를 처리해주고 catch문에서 필요한 작업을 행한 후에 throw문을 사용해서 예외를 다시 발생시킨다. 다시 발생한 예외는 이 메서드를 호출한 메서드에게 전달되고 호출한 메서드의 try-catch문에서 예외를 또 다시 처리한다.
class ExceptionEx {
public static void main(String args[]) {
try {
method1();
} catch (Exception e) {
System.out.println("main메서드에서 에외가 처리되었습니다.");
}
} // main메서드의 끝
static void method1() throws Exception {
try {
throw new Exception();
} catch (Exception e) {
System.out.println("method1메서드에서 예외가 처리되었습니다.");
throw e; // 다시 예외를 발생시킨다.
}
} // method1메서드의 끝
}
한 예외가 다른 예외를 발생시킬 수도 있다. 예외 A가 예외B를 발생시켰다면 A를 B의 '원인 예외(cause exception)'라고 한다.
try {
startInstall(); // SpaceException 발생
copyFiles();
} catch (SpaceException e) {
InstallException ie = new InstallException("설치중 예외발생");
// 예외 생성
ie.initCause(e);
// InstallException의 원인 예외를 SpaceException으로 지정
throw ie; // InstallException을 발생시킨다.
먼저 InstallException을 생성한 후에, initCause()로 SpaceException을 InstallException의 원인 예외로 등록한다. 그리고 'throw' 로 이 예외를 던진다.