프로그램을 만든 프로그래머가 상정한 정상적인 처리에서 벗어나는 경우, 이를 처리하기 위한 방법을 예외(Exception)이라 한다.
두 수를 나누는 메소드를 만들어보자. 두 수 10과 0을 매개변수로 입력받고 메인에서 메소드를 호출하면 에러가 발생한다.
public void divide(){
System.out.print("계산결과는 ");
System.out.print(this.left/this.right); // 에러
System.out.print(" 입니다."); // 에러
}
이는 10/0
을 했기 때문에 발생한 에러이다. 이 메소드의 바디를 다음과 같이 변경해보자.
public void divide(){
try {
System.out.print("계산결과는 ");
System.out.print(this.left/this.right);
System.out.print(" 입니다.");
} catch(Exception e){
System.out.println("오류가 발생했습니다 : "+e.getMessage());
}
위의 코드처럼 메소드 divide의 본문을 수정한 뒤, 메인에서 매개변수로 (10,0), (10,5)를 차례로 입력해보자. (10,0)에서는 계산결과는 오류가 발생했습니다 : / by zero
가 출력되고, 계산결과는 2 입니다.
가 출력된다. 이번 코드를 실행하면, 위의 예제와 달리 오류가 발생하지 않는다. 마치 정상적인 에플리케이션인 것처럼 동작하고 있다. 이는 try-catch
문을 사용했기 때문이다.
try-catch
문은 예외에서 핵심적인 역할을 담당하는 문법적인 요소이다.
try {
예외의 발생이 예상되는 로직
} catch (예외 클래스 인스턴스) {
예외가 발생했을 때 실행되는 로직
}
try 안에는 예외 상황이 발생할 것으로 로직을 위치시킨다.
catch안에는 예외가 발생했을 때 뒷수습을 하기 위한 로직이 위치한다.
} catch(Exception e){
System.out.println("오류가 발생했습니다 : "+e.getMessage());
}
위의 코드에서 e는 변수이다. 이 변수 앞의 Exception은 e의 데이터 타입을 의미한다. Exception은 자바에서 기본적으로 제공하는 클래스로 java.lang
에 속해있다.
e.getMessage()는 자바가 전달한 인스턴스의 메소드 중 메세지를 호출하는 코드인데, getMessage는 오류의 원인을 사람이 이해하기 쉬운 형태로 리턴하도록 약속되어 있다.
e.getMessage()
e.toString()
java.lang.ArithmeticException
예외 상황이 발생할 것이다.e.printStackTrace()
같은 로직임에도 상황에 따라서 다른 예외가 발생할 수 있다.
조건문 else if처럼 여러 개의 catch를 하나의 try 구문에서 사용할 수 있다.
class Text{
private int[] arr_ex = new int[3];
Text() {
arr_ex[0] = 0;
arr_ex[1] = 10;
arr_ex[2] = 20;
}
public void z(int num1, int num2) {
try {
System.out.println(arr_ex[num1]/arr_ex[num2]);
} catch(ArrayIndexOutOfBoundsException e) {
System.out.println("ArrayIndexOutOfBoundsException");
} catch(ArithmeticException e) {
System.out.println("ArithmeticException");
} catch(Exception e) {
System.out.println("Exception");
}
}
}
public class arr {
public static void main(String[] args) {
Text text = new Text();
text.z(10,0); // ArrayIndexOutOfBoundsException
text.z(1,0); // ArithmeticException
text.z(2,1); // Exception
}
}
catch문을 사용할 때에도 주의해야 할 점이 있다. 위의 코드에서 메소드z의 코드를 수정해보자.
public void z(int num1, int num2) {
try {
System.out.println(arr_ex[num1]/arr_ex[num2]);
} catch(Exception e) {
System.out.println("Exception");
} catch(ArithmeticException e) {
System.out.println("ArithmeticException");
} catch(ArrayIndexOutOfBoundsException e) {
System.out.println("ArrayIndexOutOfBoundsException");
}
}
메소드z를 위의 코드처럼 바꾼 후, 다시 실행해보자.
Exception은 ArrayIndexOutOfBoundsException, ArithemeticException 보다 포괄적인 예외를 의미하기에 Exception 이후에 등장하는 catch문은 실행될 수 없다.
try-catch문에 finally까지 사용할 수 있다.
try{
예외의 발생이 예상되는 로직
} catch (예외클래스 인스턴스) {
예외가 발생했을 때 실행되는 로직
} finally {
예외여부와 관계없이 실행되는 로직
}
finally
는 try 구문에서 예외가 발생하는 것과 상관없이, 언제나 실행되는 로직이다. 이번에는 finally를 이용하여 메소드z의 내용을 수정해보자.
public void z(int first, int second){
try {
System.out.println(arr[first] / arr[second]);
} catch(ArrayIndexOutOfBoundsException e){
System.out.println("ArrayIndexOutOfBoundsException");
} catch(ArithmeticException e){
System.out.println("ArithmeticException");
} catch(Exception e){
System.out.println("Exception");
} finally {
System.out.println("finally");
}
}
예외와 상관없이 try내의 구문이 실행되면 finally가 실행된다.
finally는 특정 작업을 예외와 상관없이 반드시 끝내줘야 하는 경우에 사용한다.
public static void main(String[] args) {
BufferedReader bReader = new BufferedReader(new FileReader("out.txt"));
String input = bReader.readLine();
System.out.println(input);
}
코드를 작성하다보면 로직에 대한 예외 처리가 필요함을 알려주는 경우가 있다. 예를 들어 다음과 같이 결과가 나온 경우가 예외 처리가 필요함을 알려주는 것이다.
Exception in thread "main" java.lang.Error: Unresolved compilation problems:
Unhandled exception type FileNotFoundException
Unhandled exception type IOException
Unhandled exception type FileNotFoundException
라는 것은 FileReader
와 관련된 로직에 예외처리가 필요하다는 뜻이다.
자바 공식문서를 이용하여 FileReader
에 대해 알아보자.
Parameters아래에 Throws라는 것이 있다.
Throws에는 어떠한 경우에 오류가 발생하는지에 대해 나와있다. 사진 속 내용은 생성자 FileReader의 인자 fileName의 값에 해당하는 파일이 디렉토리이거나 어떠한 이유로 사용할 수 없으면 FileNotFoundException
을 발생시킨다는 것이다.
FileReader의 생성자가 동작할 때 파일을 열 수 없는 경우가 생길 수 있고, 이 경우 생성자 FileReader에서는 이 문제를 처리할 수 없어 처리를 생성자의 사용자에게 위임하겠다라는 것이다. 이를 throw(던진다)라고 표현하다. 따라서 API의 사용자는 예외에 대한 처리를 반드시 해주어야 한다.
public static void main(String[] args) {
BufferedReader bReader = null;
String input = null;
try {
bReader = new BufferedReader(new FileReader("out.txt"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try{
input = bReader.readLine();
} catch (IOException e){
e.printStackTrace();
}
System.out.println(input);
}
위에서 예외 처리 방법으로 'try-catch-finally'를 배웠다. 이 방법 외에도, throw를 사용하는 방법이 있다. throw는 예외 처리를 다음 사용자에게 넘기는 것이다.
import java.io.*;
class B{
void run() {
BufferedReader bReader = null;
String input = null;
try {
bReader = new BufferedReader(new FileReader("out.txt")); // FileReader의 생성자
} catch(FileNotFoundException e) {
e.printStackTrace();
}
try {
input = bReader.readLine();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(input);
}
}
class C{
void run() {
B b = new B();
b.run();
}
}
public class excode {
public static void main(String[] args) {
C c = new C();
c.run();
}
}
import java.io.*;
class B{
void run() throws IOException, FileNotFoundException{
BufferedReader bReader = null;
String input = null;
bReader = new BufferedReader(new FileReader("out.txt")); // FileReader의 생성자
input = bReader.readLine();
System.out.println(input);
}
}
class C{
void run() throws IOException, FileNotFoundException{
B b = new B();
b.run();
}
}
public class excode {
public static void main(String[] args) {
C c = new C();
try {
c.run();
} catch (FileNotFoundException e) {
System.out.println("out.txt 파일은 설정 파일 입니다. 이 파일이 프로젝트 루트 디렉토리에 존재해야 합니다.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
try-catch
문이 제거되고 throws IOException, FileNotFoundException
이 추가되었다. 이는 B.run 내부에서 IOException, FileNotFoundException
의 예외가 발생하면 이에 대한 처리를 B.run의 사용자에게 위임하는 것이다. B.run의 사용자는 C.run이다.
class Calculator{
int left, right;
public void setOprands(int left, int right){
this.left = left;
this.right = right;
}
public void divide(){
if(this.right == 0){
throw new ArithmeticException("0으로 나누는 것은 허용되지 않습니다.");
}
try {
System.out.print("계산결과는 ");
System.out.print(this.left/this.right);
System.out.print(" 입니다.");
} catch(Exception e){
System.out.println("\n\ne.getMessage()\n"+e.getMessage());
System.out.println("\n\ne.toString()\n"+e.toString());
System.out.println("\n\ne.printStackTrace()");
e.printStackTrace();
}
}
}
public class CalculatorDemo {
public static void main(String[] args) {
Calculator c1 = new Calculator();
c1.setOprands(10, 0);
c1.divide();
}
}
throw는 예외를 발생시킨다. throw뒤에는 예외 정보를 가지고 있는 예외 클래스가 위치하며, 자바 가상 머신이 이 클래스를 기준으로 어떤 catch 구문을 실행할 것인지를 결정한다. 실행되는 catch 구문에서는 예외 클래스를 통해 예외 상황의 원인에 대한 다양한 정보를 얻을 수 있다.
자바 API문서를 통해 클래스Exception의 하위 클래스들에 대해서 살펴보자.
IOException은 try-catch문이나 throw를 해야만 한다. 같은 Exception일지라도 IOException처럼 특이한 것들이 있다.
클래스ArithmeticException은 Exception의 하위 클래스이다. 클래스 Exception은 더 많은 예외 상황을 포괄하지만, 클래스 ArithmeticException은 더 구체적인 상황을 특정한다. 상속 관계를 자세히 살펴보면 java.lang.Throwable
클래스가 있다. 클래스Throwable의 API문서 내용을 확인해보자.