자바에서 발생할 수 있는 에러들
Run-time error은 errors와 exceptions으로 분류된다.
Error
프로그램이 더 이상 진행을 할 수 없는 상태
ex> out of memory, stack overflow
Exceptions
에러가 발생하였으나 해결할 방법이 존재하는 경우
ex> arithmetic exception(0으로 나누는 경우), class cast exception, null pointer exception, index out-of-bounds exception
그래서 프로그래머는 handler을 선언하여서 exception이 발생하였을 때를 대처할 수 있다.
자바에서는 이러한 예외도 객체화 되어 있다. 그래서 위의 표와 같이 Throwa ble 클래스 밑에 Error과 Exception 그리고 RuntimeException이 존재
RuntimeException :
ArrayIndexOutOfBoundsException, NullPointerException, ClassCastException, Arithmetic Exception 등이 존재
Exception :
FileNotFoundException, ClassNotFoundException, DataFormatException
자바에서 에러를 처리하기 위해 try-catch블록을 사용하여 try 한다음 에러가 발생하면 catch에서 처리하여 프로그램을 계속 진행시키어 준다.
try {
// 에러가 날 수 있는 것을 여기에 삽입
} catch (Exception1 e1) {
// Exception1에 해당되는게 발생시 여기에서 처리
} catch (Exception2 e2) {
// statements that will be executed when Exception2 occurs.
try { } catch (Exception3 e3) { }
// try-catch blocks can be nested. In that case, the parameters (e2 and e3) must be different.
} catch (ExceptionN eN) {
// statements that will be executed when ExceptionN occurs.
}
public class Lecture {
public static void main(String args[]) {
int number = 100;
int result = 0;
for(int i=0; i<10; i++) {
result = number / (int)(Math.random() * 10);
System.out.println(result);
}
}
}
위와 같이 0~90 사이의 숫자로 나누어 주다가 0으로 잘 못 나누어 준 경우 위와 같이 에러 발생 출력문이 나올 수 있다.
그래서 이를 해결하기 위해 다음과 같이 작성해 주어야 한다.
public class Lecture {
public static void main(String args[]) {
int number = 100;
int result = 0;
for(int i=0; i<10; i++) {
try {
result = number / (int)(Math.random() * 10);
System.out.println(result);
// 예외 발생시 여기에서 처리
} catch (ArithmeticException e) {
System.out.println("0");
}
}
}
}
catch 블락에 대해서 다시금 생각해 보자
public class Lecture {
public static void main(String args[]) {
try {
System.out.println(3);
System.out.println(0/0); // ArithmeticException!
System.out.println(4);
} catch (Exception e) {
System.out.println(5);
}
}
}
지금 Exception 객체를 받는 catch문이 선언되어 있다. 그런데 발생한건 ArithmeticException이다. 즉 subclass에 해당하는 것은 superclass에서 모두 받아 줄 수 있다는 것을 이야기 한다.
그렇다면 만약 예외경우가 여러개이면 어떻게 할까?
public class Lecture {
public static void main(String args[]) {
try {
System.out.println(3);
System.out.println(0/0); // ArithmeticException!
System.out.println(4);
} catch (ArithmeticException e) {
System.out.println("ArithmeticException occurred!");
} catch (Exception e) {
System.out.println("Exception occurred!");
}
}
}
이 경우에는 위와 같이 작성하고 첫번째 catch 블락부터 에러를 처리 할 수 있는지 확인한다. 여기에서 중요하게 봐야하는 점은 subclass는 가급적 superclass 위에 위치시키는 것이 좋다.
에러가 났을 때 우리는 무엇을 하는가 인데, 보통의 printStackTrace()와 getMessage()를 많이 한다.
public class Lecture {
public static void main(String args[]) {
try {
System.out.println(3);
System.out.println(0/0); // ArithmeticException!
System.out.println(4);
} catch (ArithmeticException ae) {
ae.printStackTrace();
System.out.println("exception message: " + ae.getMessage());
}
}
}
printStackTrace : 에러가 생기기 까지 어떤 경로를 따라서 이 위치까지 왔는지를 확인하는 함수
getMessage : 객체로 부터 에러 메세지를 가져온다.
예외 경우를 일부러 만들 수도 있다.
public class Lecture {
public static void main(String args[]) {
try {
Exception e = new Exception("I created the exception."); // becomes the exception message.
throw e;
} catch (Exception e) {
System.out.println("exception message: " + e.getMessage());
e.printStackTrace();
}
System.out.println("The program terminated normally.");
}
}
예외 경우를 다룰 객체를 만들어 놓고 throw를 사용하여서 Exception을 처리한다.
File Not Found와 같은 경우처럼 반드시 Exception을 하여야만 하는 경우가 존재한다.
public class Lecture {
public static void main(String args[]) {
throw new Exception("My exception.");
}
} // compile error!
public class Lecture {
public static void main(String args[]) {
throw new ArithmeticException("My exception.");
}
} // no error!
그럼 어떻게 위의 경우를 구분할까?
Exception 클래스의 subclass 중에서 Runtime Exception을 제외하고 나머지는 Mandatory이다.
Runtime Exception의 대표적인 경우가 ArithmeticException이다.
예외 처리를 하지 않는 방법도 존재한다.
public class Lecture {
public static void main(String args[]) {
method1();
}
static void method1() {
method2(); // Error: must handle Exception or throw Exception.
}
static void method2() throws Exception {
throw new Exception();
}
} // compile error!
throws Exception은 에러가 난 경우를 처리하지 않겠다는 의미이다. 그렇다면 method2에서는 에러처리를 하지 않아도 되지만 그 경우에는 method2를 호출한 method1이 에러처리를 해주어야만 한다.
그러면 method1에서도 throws Exception을 한다면??
public class Lecture {
public static void main(String args[]) {
method1(); // Error: must handle Exception or throw Exception.
}
static void method1() throws Exception {
method2();
}
static void method2() throws Exception {
throw new Exception();
}
} // compile error!
결국 main에서 에러가 나게 된다.
만약 main까지 throws Exception을 한다면??
public class Lecture {
public static void main(String args[]) throws Exception {
method1();
}
static void method1() throws Exception {
method2();
}
static void method2() throws Exception {
throw new Exception();
}
} // no error at compile time but will cause program to crash at run time.
실행은 되지만 crash가 난다.
그래서 위의 문제를 해결하기 위해 다음과 작성해주어야지 문제가 발생하지 않는다.
public class Lecture {
public static void main(String args[]) {
method1();
}
static void method1() {
try {
method2();
} catch(Exception e) {
System.out.println("Exception handled in method1");
e.printStackTrace();
}
}
static void method2() throws Exception {
throw new Exception();
}
} // OK! method1 handles the Exception
여기에서 한가지 더 짚고 넘어가자. 우리는 전 시간에 파일을 다루기 위한 메소드를 선언하면서 throws IOException을 선언했던것을 기억할 것이다. 그런데 이렇게 하면 좋지 않은 것이 FileOutputStream 내부에는 throws FileNotFoundException이 선언되어 있기 때문이다.
import java.io.FileOutputStream;
import java.io.IOException;
public class Lecture {
public static void main(String[] args) throws IOException {
FileOutputStream output = new FileOutputStream("src/cse3040/out.txt");
String str = "hello world";
byte[] bytes = str.getBytes();
output.write(bytes);
output.close();
}
}
그래서 위와 같은 코드에도 가급적 try-catch문 선언이 필요하다.
이제 finally Block에 대해서 알아보자
finally block은 try block에서 exception이 발생하든 안하든 상관없이 반드시 실행되는 block이다.
try {
// statements that can cause exceptions.
} catch (Exception1 e1) {
// statements for handling Exception1
} finally {
// this block is executed whether or not an exception occurs in the try block.
// this block must be placed at the end of a try-catch block.
}
하지만 아래와 같이 try 안에 return을 수행하여야 하는 경우는 어떻게 될 것인가? 결론은 finally를 수행한다음 return을 하게 된다.
public class Lecture {
public static void main(String args[]) {
Lecture.method1();
System.out.println("returned to main method after calling method1.");
}
static void method1() {
try {
System.out.println("the try block of method 1 is being executed.");
return;
} catch(Exception e) {
e.printStackTrace();
} finally {
System.out.println("the finally block of method 1 is being executed.");
}
}
}
위와 유사하게 이러한 코드를 File IO 에도 사용하여야 한다. 기존에는 BUfferedReader을 사용하기 위해서 main에 throws IOException을 적어 주었던 것이 기억나는가? 이거 역시 사실은 try-catch문으로 모두 잡아 주어야 한다.
여기에서 한가지 주의할점!! try-catch 내에 선언된 변수는 지역변수이다. 파일 IO 변수는 try-catch 외부에 선언하고 나중에 try-catch 내부에서 사용하자
import java.io.FileInputStream;
import java.io.IOException;
public class Lecture {
public static void main(String[] args) {
byte[] b = new byte[1024];
FileInputStream input = null;
try {
input = new FileInputStream("src/kr/ac/sogang/icsl/aaa.txt");
input.read(b);
System.out.println(new String(b));
} catch(IOException e) {
e.printStackTrace();
} finally {
try {
input.close();
} catch(Exception e) {
e.printStackTrace();
}
}
System.out.println("The program exited normally.");
}
}