예외란 오류의 한 종류이다.
우리가 코딩을 하고 실행을 하면 컴파일러가 문법오류등 기본적인 오류를 잡아준다. 그러나, 컴파일러는 기본적인 오류만 잡아줄 뿐, 코드가 동작하면서 발생할 수 있는 잠재적인 오류까지는 잡아줄 수 없다.
예를 들어보자.
public class Exception {
public static void main(String[] args){
int arr[] = new int[10];
int i;
for(i=0; i<10; i++){
arr[i]=i+1;
}
for(i=0; i<11; i++){// 오류가 발생하는 지점(배열 범위 초과)
System.out.println(arr[i]);
}
}
}
위 코드를 실행하면 분명히 오류가 발생하지만 컴파일러는 이를 잡지 못한다. 이와 같이 프로그램이 실행중에 발생하는 오류를 런타임 오류
라고 한다. 이런 오류가 발생할 경우 프로그램이 강제종료 되거나, 프로그램이 정지가 되곤 한다. 이때, 이런 문제를 방지하기 위해 오류가 발생할 수 있을것 같은 부분
에 예외
처리를 해서 오류가 발생했을때 어떤 행동을 할지 대처를 할 수 있다. 물론, 모든 오류에 전부 대처할 수 있는것이 아니다. 예를들어 메모리가 부족하거나, 스택 오버플로우같이 대처가 힘들고, 프로그램의 치명적인 문제를 일으킬 수 있는 경우, 프로그램을 강제종료 해야 할 경우가 발생할 수 있으나, 심각하지 않은 오류의 경우 충분히 대처를 할 수 있다.
위의 코드를 예를 들면 배열의 요소에 직접 접근하기 때문에 배열 접근에서 오류가 발생할 수 있다. 그렇기 때문에 다음과 같이 예외를 처리할 수 있다.
public class Exception {
public static void main(String[] args){
int arr[] = new int[10];
int i;
try{// 오류가 발생할것 같은 부분
for(i=0; i<10; i++){
arr[i]=i+1;
}
} catch (ArrayIndexOutOfBoundsException e) {// 배열 범위에 관한 오류가 발생할 경우
System.out.println("배열 범위 초과.");
}
try{// 오류가 발생할것 같은 부분
for(i=0; i<11; i++){
System.out.println(arr[i]);
}
}catch (ArrayIndexOutOfBoundsException e) {// 배열 범위에 관한 오류가 발생할 경우
System.out.println("배열 범위 초과.");
}
}
}
밑에서 자세히 배우겠지만 오류가 발생할것 같은 부분을 관심법으로 예측하고, 해당 부분에 try
라는 것과 catch
를 사용하면 프로그램이 도중에 멈추거나 종료되지 않고 예외처리에 따라 정상적으로 동작을 한다.
java에서는 프로그램이 동작하면서 발생할 수 있는 오류의 종류를 아래와 같이 클래스로 정의했다.
프로그램이 동작하면서 발생하는 오류의 종류는 상당히 많다. 따라서, java에서도 예외 클래스는 상당히 많이 존재한다. 이 모든것을 전부 외우기 보다는 주로 사용되는 예외를 위주로 숙지하는 것이 바람직하다.
위 그림에서 RuntimeException
이란 것이 있을 것이다. 이 예외는 주로 프로그램이 실행중에 발생하는 오류들로, 주로 프로그래머의 실수에 의해 발생하는 경우가 많다. 대표적으로 위에서 봤던 배열의 범위 밖의 접근하는 ArrayIndexOutOfBoundsException
이나, 값이 null인 참조변수에 접근하는 NullPointerException
, 클래스간 형 변환을 잘못하는 ClassCastException
, 정수를 0으로 나누는 ArithmeticException
등이 대표적이다.
try-catch란 예외가 발생할 것 같은 지점의 예외를 처리하는 구문이다.
try-catch
구문은 예외가 발생할 것 같은 부분에 대해서 예외처리를 하는 구문으로, 아래와 같이 try
를 이용해 예외가 발생할 것 같은 부분을 설정하고, catch
를 이용해 예외를 처리할 수 있다.
public class Exception {
public static void main(String[] args){
try{
// 예외가 발생할것 같은 부분
} catch (예외_이름) {
// 예외처리
}
}
}
try-catch
구문에서는 try
를 실행하고, catch
에 있는 예외가 발생했을 경우 catch
구문을 실행한다. 만약 예외가 발생하지 않을 경우에는 그냥 try
구문을 실행하고 다음으로 넘어간다.
직접 코드를 작성하면서 알아보자.
public class Exception {
public static void main(String[] args){
int num=100;
int result;
for(int i=0; i<10; i++){
result=num/(int)(Math.random()*10);
System.out.println("number: "+result);
}
}
}
위 코드를 보면 큰 문제가 없어보이나, 실제로 동작을 시키면 다음과 같은 예외가 발생한다.
Exception in thread "main" java.lang.ArithmeticException: / by zero
at exception.Exception.main(Exception.java:9)
Math.random()
메소드는 0~1 사이의 랜덤한 값을 반환하는 메소드다. 즉, 0을 반환할 수 있으므로, 숫자를 0으로 나누게 되면서 예외가 발생한 것이다. 우리가 미처 처리하지 못한 예외는 이와 같이 jvm의 예외처리기를 통해 화면으로 예외 내용을 출력한다.
만약 위 코드에서 예외 처리를 해서 도중에 프로그램이 종료되지 않게 하기 위해서는 다음과 같이 try-catch
구문을 사용할 수 있다.
public class Exception {
public static void main(String[] args){
int num=100;
int result;
for(int i=0; i<10; i++){
try{
result=num/(int)(Math.random()*10);// 예외 발생할것 같은 부분
System.out.println("number: "+result);
}catch (ArithmeticException exception){// 예외처리
System.out.println("예외발생! 0으로 나눌 수 없습니다.");
}
}
}
}
output
number: 11
number: 14
number: 20
number: 11
number: 11
예외발생! 0으로 나눌 수 없습니다.
number: 12
number: 14
number: 16
number: 33
이와같이 예외가 발생하면 try
에 있는 코드를 중단하고 예외를 처리하고, 만약 예외가 발생하지 않으면 try
에 있는 코드를 실행하는 방식으로 try-catch
가 동작한다.
또한, catch
에서 동작하는 코드에서 또 다른 예외가 발생할 수 있으므로, 중첩 조건문과 같이catch
코드 내부에도 try-catch
구문이 들어갈 수 있다.
try-catch
구문에서 예외가 발생한 경우와 발생하지 않은 경우의 흐름은 다음과 같다.
try
블럭에서 예외가 발생한 경우 발생한 예외가 catch
블럭에 있는지 확인한다.
만약 catch
블럭에서 동일한 예외가 있다면 아래 코드와 같이try
블럭을 종료하고 해당 catch
블럭으로 이동해 예외를 처리한다.
public class Exception {
public static void main(String[] args){
try{
System.out.println(10/3);
System.out.println(5/2);
System.out.println(0/0);
System.out.println("코드 중단 안됨.");
System.out.println(-5/2);
}catch (ArithmeticException exception){
System.out.println("예외발생! 0으로 나눌 수 없습니다.");
}
}
}
output
3
2
예외발생! 0으로 나눌 수 없습니다.
아래와 같이 만약 catch
블럭에 발생한 예외가 없다면 예외처리는 진행되지 않는다. 즉, 쉽게말해 프로그램이 중단된다는 것이다.
public class Exception {
public static void main(String[] args){
try{
System.out.println(10/3);
System.out.println(5/2);
System.out.println(0/0);
System.out.println("코드 중단 안됨.");
System.out.println(-5/2);
}catch (ArrayIndexOutOfBoundsException exception){
System.out.println("예외발생!");
}
}
}
output
3
2
Exception in thread "main" java.lang.ArithmeticException: / by zero
at exception.Exception.main(Exception.java:9)
위 코드의 결과를 보면 코드가 중단될 뿐 아니라 프로그램 자체가 종료가 된 것을 알 수 있다.
즉, 프로그램이 비정상적으로 도중에 종료되지 않게 하려면 상황에 일어날 법한 예외를 관심법으로 잘 예측하는 것이 중요하다.
이와같이 야투경을 끼고 차갑고 어두운 코드속을 잘 분석하자.
이와같이 try-catch
를 통해 예외처리를 해도 우리가 예상하지 못한 예외가 발생하면 프로그램이 종료되기 때문에 일어날 법한 예외를 유추해서 알맞은 예외처리를 하는 것이 중요하다.
예외가 발생하지 않은 경우는 catch
블럭을 거치지 않고 코드가 정상적으로 진행이 된다.
위에서 예외에 대해서 예외클래스
라고 한 적이 있다. 만약 예외가 발생하면 내부적으로 해당하는 예외클래스의 인스턴스가 생성된다. 그 다음으로 해당 예외 클래스가 catch
블럭에 있는지 instanceof
연산자를 이용해 확인하는 방식으로 예외를 처리하는 구조다.
이때, 모든 예외 클래스는 Exception
클래스의 자손 클래스 이므로 Exception
클래스로 catch
를 하면 모든 예외가 전부 감지가 된다.
public class ExceptionStudy {
public static void main(String[] args){
try {
System.out.println(10 / 3);
System.out.println(5 / 2);
System.out.println(0 / 0);
System.out.println("코드 중단 안됨.");
System.out.println(-5 / 2);
} catch (Exception e) {// 모든 예외 감지
System.out.println("예외 발생!");
if(e instanceof ArithmeticException){// 연산 오류일 경우
System.out.println("연산 예외 발생.");
}
}
}
}
output
3
2
예외 발생!
연산 예외 발생.
catch
구문에서 여러 예외를 한번에 처리하고자 할때는 |
(shift + \)를 통해 처리할 수 있다.
public class ExceptionStudy {
public static void main(String[] args){
try {
// code
} catch (Exception1 | Exception2 e) {
// 예외처리
}
}
}
finally
블럭의 경우 예외 여부와 상관없이 실행하고자 하는 부분에서 사용할 수 있다.
아래와 같은 코드의 경우 try
에서 발생하는 예외로 인해 calculate end
라는 문자가 출력이 안된다.
public class ExceptionStudy {
public static void main(String[] args){
try {
System.out.println(10 / 3);
System.out.println(5 / 2);
System.out.println(0 / 0);
System.out.println("calculate end.");
} catch (ArithmeticException e) {
System.out.println("연산 예외 발생!");
}
}
}
output
3
2
연산 예외 발생!
이때, 예외와 관계없이 try
구문의 마지막에 있는 System.out.println("calculate end.");
를 동작시키고 싶은 경우 다음과 같이 finally
구문을 사용할 수 있다.
예외 발생
public class ExceptionStudy {
public static void main(String[] args){
try {
System.out.println(10 / 3);
System.out.println(5 / 2);
System.out.println(0 / 0);
} catch (ArithmeticException e) {
System.out.println("연산 예외 발생!");
} finally {
System.out.println("calculate end.");
}
}
}
output
3
2
연산 예외 발생!
calculate end.
예외 발생x
public class ExceptionStudy {
public static void main(String[] args){
try {
System.out.println(10 / 3);
System.out.println(5 / 2);
//System.out.println(0 / 0);
} catch (ArithmeticException e) {
System.out.println("연산 예외 발생!");
} finally {
System.out.println("calculate end.");
}
}
}
output
3
2
calculate end.