Java/Java 기본

예외 (Exception)

검정비니 2018. 6. 21. 01:47
728x90
반응형

예외 (Exception)



1. 예외란?


프로그래밍 언어의 문법에 맞지 않게 프로그램을 작성하면 컴파일 오류가 발생한다. 그러므로 실행하기 전에 문법에 맞지 않게 작서오딘 오류는 미리 걸러내어야 한다. 그러나 프로그램 실행 중 계속 변하는 배열의 인덱스가 범위를 벗어나는 것은 컴파일 시점에서 걸러낼  수 없다. 이퍼럼 프로그램 실행 중에 발생하는 런타임 오류(runtime error)는 미리 걸러낼 수가 없어 자바에서는 예외(Exception)라는 것을 사용하여 처리하낟. 예외란 프로그램의 실행 중에 런타임 오류틔 발생을 응용프로그램에게 알리는 행위이다.


예외가 발생하는 몇가지 경우를 나열해보자.


1) 정수를 0으로 나누는 경우

2) 배열의 크기보다 큰 인덱스로 배열의 원소를 접근하는 경우

3) 파일의 마지막 부분에서 데이터를 읽으려고 하는 경우


프로그램 실행 중 예외가 발생할 때, 프로그램에서 예외 처리를 따로 하지 않으면 프로그램은 강제 종료가 된다.



1 - 1. 컴파일러 체크 유무에 따른 예외 종류


1) Checked Exception :

컴파일러에 의해서 미리 체크가 되어지는 예외. 예외 처리를 하지 않은 상태에서 컴파일을 시도할 경우, 컴파일 오류가 발생하게 된다. 예외 처리에 대해서는 뒤에서 자세히 언급하도록 하겠다.

i.e. IOException, SQLException, FileNotFoundException, etc


2) Unchecked Exception :

자바 소스에서 예외 처리를 해주지 않아도 된다. JVM 실행시 예외 발생을 인지하여 그에 해당하는 예외 객체를 생성하고 처리해 준다. 이러한 예외들은 컴파일러가 미리 체크해 주지 못하므로, 자바 코드를 작성할 때에 발생할 수 있는 예외들에 대해서 미리 예상하면서 코드를 짜야한다.

i.e. ArithmeticException, ArrayIndexOuutOfBounndsException, etc



2. 예외 발생 사례


실제 예외가 발생하는 사례를 들어 예외를 발생시키는 경우와 예외가 발생하였을 때의 상황을 알아보도록 하자.


앞서 말했듯이, 정수를 0으로 나누게 되면 예외가 발생하게 된다. 이 때 발생하는 예외는  ArithmeticException이라는 이름의 예외이다. 이 예외는 이름에서 알 수 있듯이 산술 연산 (arithmetic operation) 에 의해서 발생하는 예외이다. 다음의 코드를 돌려보고 직접 예외의 발생을 확인해 보도록 하자.


public class ExceptionExample1 {

public static void main(String[] args) {

int divisor = 0;

int dividend = 3;

int result = dividend / divisor; //ArithmeticException 발생!

System.out.println("3 / 0 = " + result); // 앞에서 발생한 예외에 의해서 이 문장이 실행되기 전에 프로그램이 종료된다.

}

}


예외가 발생하게 되면 프로그램이 종료된다고 했다. 그렇다면, 발생하는 예외를 처리하여 강제로 프로그램이 종료되는 것을 방지하는 방법을 알아보자.




3. 예외 처리, try-catch-finally 문


예외 처리란, 발생한 예외에 대해서 개발자가 작성한 프로그램 내에서 대응하는 것을 말한다. 자바에서 예외 처리 시 try-catch-finally 문을 사용한다. 


try-catch-finally 사용법 :

오류가 발생할 예상 부분을 try 블록으로 감싼다. 그 후에, 발생할 오류와 관련된 Exception을 타겟으로 하는 catch 블록을 구현해서 예외를 처리한다. finally 블록은 예외가 발생하던 발생하지 않던 반드시 실행되어야 할 코드들을 입력해 놓는 블록이다. 다만 finally문의 경우, 굳이 사용할 것 같지 않으면 생략해도 상관 없다.



try-catch-finally 문의 형태는 다음과 같다.


try {

...

실행문

...

}

catch( 처리할 예외 타입 선언) {

예외 처리문

}

finally {

예외 발생 여부와 관계 없이 실행시킬 실행문

}


이 예외 처리문은 실제 코드에서는 다음과 같은 형태를 가진다.


try {

...

} catch (Exception e) {

...

} finally {

...

}


위의 catch 문을 보게 되면 "Exception e" 라는 이상하게 생긴 부분이 눈에 띌 것이다. 앞서 말했듯이, 자바 가상 기계는 프로그램 실행 중에 예외가 발생하게 되면 그 예외에 해당하는 예외 객체를 생성한다. 이때 생성된 객체가 만약 저 catch 문에서 처리하고자 하는 예외의 타입과 일치할 경우, 저 catch 문의 "Exception e"라는 레퍼런스 변수가 생성된 예외 객체를 가리키게 된다. 이 객체의 수명은 catch 문이 끝나는 순간 끝나게 된다.


만약, try 문 내에 있는 실행문에 의해서 다수의 예외가 발생 가능하다면 어떻게 처리해야 할까? 이때에는 3가지 방법이 있다. (이 방식들을 설명하는 과정에서 "상속"이나 "부모 자식 클래스"등 아직 다루지 않은 주제들에 대한 내용이 나오게 되는데, 만약 아직 모른다면 지금은 굳이 이해하려 애를 쓸 필요는 없다. 나중에 상속에 대해서 학습한 후에 다시 보게 되면 금방 이해를 하게 될 것이다.)


1) 가장 상위의 예외 클래스를 이용해서 한꺼번에 처리한다. 예외 클래스들은 모두 Exception 클래스를 상속 받으므로, 예외 클래스에 Exception을 두게 되면 어떤 오류를 ㅂ라생하든지 간에 하나의 catch 블록에서 모든 오류를 처리할 수 있다. 다만, 이것은 아주 효율적인 방식이 아니기 때문에, 내가 쓴 실행문에서 어떤 오류가 발생할지 다 예상을 할 수 없을 경우에만 이러한 방법을 사용한다.


2) 다수의 catch 문을 연결한다. 자바에서 if-else 문을 쓸 때에 else if 문을 무한정 연결할 수 있었듯이, 이 try-catch 문에서도 하나의 try 문에 다수의 catch 문들을 추가할 수 있다. 다만, 부모 예외 클래스와 자식 예외 클래스의 경우, 자식 예외 클래스에 대한 처리문을 위에다가 써야 컴파일 오류가 발생하지 않는다.


i.e.

try {

...

} catch (FileNotFoundException e) {

...

} catch (IOException e) { // IOException은 FileNotFoundException의 내용을 포함하고 있다.

...

} catch (Exception e) { //Exception은 모든 예외 클래스들의 최상위 부모이다.

...

}


3) "|" 를 이용해서 다수의 예외 객체들을 모두 묶어버리자. 자바에서 하나의 catch 문에서 여러 개의 예외를 처리하는 방법이 있는데, 바로 "|"를 이용해서 발생하는 예외를 처리하는 것이다.


i.e.

try {

...

 // 발생하는 예외가 아래의 셋 중 하나라면, 레퍼런스 변수 e의 레퍼런스 타입은 그 예외 클래스의 타입이 된다.

} catch (NullPointerException | ArrayIndexOutOfBoundsException | NumberFormatException e) {

...

}



다음의 예시 코드들을 통해서 예외 처리문을 익혀 보도록 하자.


i.e.1 ->  두 정수의 누눗셈에서 ArithmeticException을 처리하도록 만든 예제


public class ExceptionExample2 {

public static void main (String[] args) {

int divisor = 0;

int dividend = 3;

int result;

try {

result = dividend / divisor;

System.out.println("3 / 0 = " + result);

} catch {

System.out.println("0으로 나눌 수 없습니다.");

}

}

}


i.e.2 -> 범위를 벗어난 배열의 접근 (ArrayIndexOutOfBoundsException)


public class ArrayExceptionExample {

public static void main(String[] args) {

int[] intArray = new int[5];

intArray[0] = 0;


try {

for (int i = 0; i < 5; i++) {

intArray[i + 1] = i + 1 + intArray[i]; //i = 4 일 때에 예외 발생

System.out.println("intArray[" + i + "]" + "=" + intArray[i]);

}

} catch (ArrayIndexOutOfBoundsException e) {

System.out,println("배열의 인덱스가 범위를 벗어났습니다!");

}

}

}




4. 예외의 발생과 처리 의무 미루기


예외는 발생한 부분에서 try-catch-finally 문을 사용해서 처리를 해야만 한다. 그런데, 이러한 처리를 굳이 예외가 발생한 메소드나 클래스에서 하고 싶지 않을 경우에는 throws 키워드를 이용한다. throws는 예외가 발생하는 메소드를 호출한 곳에서 예외를 처리하도록 의무를 미룰 수 있게 해주는 키워드이다.


throws 키워드는 예외 처리의 의무를 현재 예외가 발생한 메소드에서 그 메소드를 호출한 메소드로 전달하는 역할을 한다고 보면 된다.


i.e.


public void funcA() {

try {

funcB(); //thorws에 의해서 여기서 예외를 처리해 줘야함 -> try-catch 문 사용

} catch(ArithmeticException e) {

e.printStackTrace();

}

}


public void funcB() throws ArithmeticException {  // throws 키워드로 예외 던지기

System.out.println(3/0);

}



만약, 계속 예외를 던지기만 하다가 프로그램의 시작점인 main 메소드에서도 throws를 이용해서 예외를 던지게 된다면, 자바에서 제공하는 기본 예외 처리 방식에 따라 예외가 처리되어지면서 프로그램이 종료되게 된다.


자바에는 예외를 처리하는 키워드 뿐만이 아니라 예외를 강제로 발생시키는 키워드도 있는데, throw 키워드가 바로 그것이다. 이 throw 키워드는 프로그래머가 정의한 예외를 강제로 발생시키는 역할을 한다. throw의 구문은 다음과 같다.


throw new Exception(); //Exception 대신 다른 예외 클래스도 사용 가능


일반적으로, 이 throw는 사용자가 직접 구현한 Exception 클래스를 이용해서 특정 상황에서 에러가 나도록 만들고 싶을 때에 사용한다.

반응형

'Java > Java 기본' 카테고리의 다른 글

클래스 선언과 활용  (0) 2018.06.22
객체 지향과 자바  (0) 2018.06.21
main() 메소드의 인자  (0) 2018.06.13
메소드에서 배열 리턴  (0) 2018.06.12
다차원 배열  (0) 2018.06.12