개발자가 코드를 작성하고 실행을 할 때, 가장 먼저 부딪히는 문제는 실행에 대한 오류가 발생할 때이다. 오류에 대해서는 매우 여러 종류의 상황이 있을 것이다. 개발자를 위해 대부분의 IDE는 코드 자체에 대한 문제, 문법적인 문제, 메모리에 대한 문제 등등 여러 방면의 오류 코드를 가지고 있으며 개발자에게 이러한 문제의 코드 위치를 알려주며 코드에 대한 변경을 요청한다. 이러한 여러 오류를 사전에 개발자가 파악하고 오류가 발생하였을 때 특정한 행위를 하도록 코드를 작성하는 것이 바로 예외 처리이다.
개발자가 코드를 작성하고 실행을 하였을 때 발생 가능한 오류는 크게 3가지로 나눌 수 있다.
- 논리적 에러 (Logical error)
- 컴파일 에러 (Compile-time error)
- 런타임 에러 (Runtime error)
논리적에러는 개발자에 있어 가장 불친절한(?)에러이다. 실제로 코드 실행에는 문제가 없으나 실행 결과가 원하지 않은 결과로 출력되는 경우여서 개발자의 입장에서는 매우 골치아픈 오류이다. 코드의 양이 방대한 경우에는 다른 에러보다 해결하는데 시간을 많이 사용해야 할 수 있지만 코드 실행 결과를 보며 문제가 발생한 곳을 찾아 파악하면 금방 해결 할 수도 있다. 컴퓨터의 입장에서는 코드에 대한 문제가 없어 아무런 에러 표시도 하지 않아 불친절한 에러라고 표현하였다.
컴파일에러는 프로그램을 컴파일하는 동안에 발생한 에러를 말한다. 보통 코드 자체 문법적인 오류로 많이 발생하는데 이를 Syntax Error라고 표현한다. 보통 코드의 마지막에 ';'를 빼먹는다거나 println을 prnitln으로 작성하는 맞춤법 오류로 많이 발생한다. 컴파일 에러의 경우, IDE에서 바로 빨간줄로 표시를 해주어 개발자가 코드를 작성하는 동안 바로 인지하고 수정할 수 있는 오류이다.
컴파일 단계에서 문제가 없어 프로그램 실행을 하려는데 발생하는 오류이다. 보통 프로그램 설계 미숙에서 많이 발생하고 컴파일에러와 같이 IDE에서 정확한 해결방안을 주지 않기 때문에 개발자는 디버깅을 통해 에러를 해결해야 한다. 자바에서 발생할 수 있는 런타임에러는 상황마다 굉장히 다양하므로 크게 에러와 예외로 나눌 수 있다.
보통 프로그램을 설계할 때 메모리에 대한 고려를 하지 않았을 경우 많이 발생한다. 프로그램이 실행조차 되지 않고 바로 다운되어 버리기 때문에 처음부터 하나하나 메모리에 대해 오버플로우가 발생하지 않았는지, 메모리가 부족하지는 않은지 체크해야 한다. 이를 도와주는 도구가 바로 디버깅 도구이다.
예외의 경우 IDE가 어떠한 예외가 발생하였는지 알려주고 어떠한 클래스, 메서드 부분에서 발생하였는지 알려주어 그나마 쉽게(?) 오류에 대한 해결이 가능하다. 코드의 문법적인 부분에 대하여 예외가 발생하지 않더라도 프로그램을 어떻게 설계하고 어떠한 입력 값들을 받느냐에 따라 예외가 매우 다양해 질 수 있다. 따라서 개발자는 발생할 수 있는 다양한 예외에 대해 미리 방지하고 예외가 발생할 경우에 대한 대비를 해야한다. 이를 예외 처리 (Exception Handling) 라고 한다. 프로그램을 개발하며 다양한 입력 값으로 테스트를 하겠지만 미처 생각하지 못한 부분에 대해 예외가 발생하여 오류가 나면 실제 배포시 사용자에게 있어 큰 버그가 될 수 있기 때문에 예외처리는 프로그램 설계에 있어 굉장히 중요한 요소 중에 하나이다.
자바에는 다양한 오류에 대한 런타임 에러 클래스가 존재한다. 다음을 살펴보자.
| 에러 클래스 | 설명 |
|---|---|
ArithmeticException | 산술 연산 중에 발생하는 예외로, 0으로 나누기 등의 잘못된 산술 연산 시 발생한다. |
ArrayIndexOutOfBoundsException | 배열 인덱스가 잘못된 경우에 발생하는 예외이다. 배열의 범위를 벗어나는 인덱스에 접근할 때 발생한다. |
ClassCastException | 형 변환 시에 발생하는 예외이다. 객체가 다른 타입으로 형 변환될 수 없는 경우에 발생한다. |
IllegalArgumentException | 메서드에 전달된 인수의 값이 잘못된 경우에 발생하는 예외이다. |
IllegalStateException | 객체의 상태가 메서드를 호출하기에 적절하지 않은 경우에 발생하는 예외이다. |
NullPointerException | null 값을 가진 참조 변수를 사용하려고 할 때 발생하는 예외이다. |
NumberFormatException | 숫자 형식의 문자열을 숫자로 변환할 때 발생하는 예외이다. 숫자 형식이 아닌 문자열을 변환하려고 할 때 발생한다. |
IndexOutOfBoundsException | 컬렉션에서 인덱스가 잘못된 경우에 발생하는 예외이다. 컬렉션의 범위를 벗어나는 인덱스에 접근할 때 발생한다. |
RuntimeException | 프로그램 실행 중에 발생하는 다양한 예외 클래스의 부모 클래스이다. 주로 프로그래머의 실수나 논리 오류에 의해 발생한다. |
public class ArithmeticExceptionExample {
public static void main(String[] args) {
int result = 5 / 0; // ArithmeticException 발생: 0으로 나누기
}
}
public class ArrayIndexExample {
public static void main(String[] args) {
int[] array = {1, 2, 3};
int element = array[5]; // ArrayIndexOutOfBoundsException 발생: 배열 범위를 벗어난 인덱스에 접근
}
}
public class ClassCastExceptionExample {
public static void main(String[] args) {
Object obj = "Hello";
Integer num = (Integer) obj; // ClassCastException 발생: String을 Integer로 형 변환
}
}
public class IllegalArgumentExceptionExample {
public static void main(String[] args) {
printNumber(-1); // IllegalArgumentException 발생: 잘못된 인수 값 전달
}
public static void printNumber(int num) {
if (num < 0) {
throw new IllegalArgumentException("음수는 인수로 전달할 수 없습니다.");
}
System.out.println("숫자: " + num);
}
}
public class IllegalStateExceptionExample {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.setLength(-1); // IllegalStateException 발생: 문자열 길이가 음수가 될 수 없음
}
}
public class NullPointerExceptionExample {
public static void main(String[] args) {
String str = null;
int length = str.length(); // NullPointerException 발생: null 값을 가진 참조 변수에 접근
}
}
public class NumberFormatExceptionExample {
public static void main(String[] args) {
String str = "abc";
int number = Integer.parseInt(str); // NumberFormatException 발생: 숫자 형식이 아닌 문자열을 변환
}
}
import java.util.ArrayList;
public class IndexOutOfBoundsExceptionExample {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
int element = list.get(1); // IndexOutOfBoundsException 발생: 컬렉션의 범위를 벗어난 인덱스에 접근
}
}
이러한 예외가 발생할 대비하여 사용자는 다음과 같은 문법을 사용하여 예외처리를 할 수 있다.
예외처리의 궁극적인 목표는 프로그램이 예외로 인해 실행 중단되지 않도록 하는 것이다. 또한 특정한 버그로 인해 개발자가 의도하지 않은 값이 출력된다던지와 같은 불상사를 막기 위해 예외 처리를 해야한다.
자바에서 예외 처리를 하는 대표적인 방법은 try, catch, finally를 이용하는 것이다.
try문에서 예외가 발생할 수 있는 코드를 작성하고 catch문에서는 예외가 발생하였을 경우 어떻게 처리하는지에 대해 작성하면 된다.
마지막 finally 문에서는 예외 발생 여부에 상관없이 항상 실행되는 코드를 작성한다. finally문은 필요시에만 작성이 가능하다.
try {
// 예외가 발생할 수 있는 코드
} catch (ExceptionType e) {
// 예외 처리
} finally {
// 항상 실행되는 코드 (선택적)
}
다음과 같이 0으로 나눌 경우 ArithmeticException이 발생하는데 try catch문을 이용하여 프로그램 실행 종료를 하지 않고 해당 예외가 발생할 경우 특정 메세지가 출력되도록 할 수 있다.
try {
int result = 10 / 0; // ArithmeticException 발생
} catch (ArithmeticException e) {
System.out.println("0으로 나눌 수 없습니다.");
}
개발자가 프로그램을 작성하다 보면 원하는 입력값이 아닐 경우 강제로 예외를 발생시키고 싶은 경우가 존재한다. 예를 들면 15이하의 값을 입력해야하는데 사용자가 16을 입력할 경우, 영어를 입력해야하는데 한글로 입력한 경우 등등의 경우가 있다. 이때, 프로그램적으로의 오류가 아니더라도 개발자는 throw를 통해 강제로 예외를 발생 시킬 수 있다.
public class CustomExceptionExample {
public static void main(String[] args) {
try {
throwCustomException();
} catch (CustomException e) {
System.out.println(e.getMessage());
}
}
public static void throwCustomException() throws CustomException {
throw new CustomException("사용자 정의 예외 발생!");
}
}
class CustomException extends Exception {
public CustomException(String message) {
super(message);
}
}
또 다른 예시로는 아래와 같이 유효성 검사를 통해 예외를 발생시키는 경우이다. 개발자가 원하는 조건에 따라 예외를 발생 시킬 수 있다.
public class Example {
public static void main(String[] args) {
try {
validateAge(15);
} catch (IllegalArgumentException e) {
System.out.println("나이가 유효하지 않습니다: " + e.getMessage());
}
}
public static void validateAge(int age) {
if (age < 18) {
throw new IllegalArgumentException("나이는 18세 이상이어야 합니다.");
}
}
}
위 코드에서 validateAge() 메서드는 나이가 18세 미만일 경우 IllegalArgumentException을 발생시킨다. 이렇게 사용자가 지정한 조건에 맞지 않을 때 throw를 사용하여 예외를 발생시킬 수 있다.
throw가 예외 발생시키기였다면 throws는 예외 던지기라고 할 수 있다. try, catch문을 사용하지 않고 메서드안에서 발생할 수 있는 예외를 함수 정의 뒤부분에 throws와 함께 작성하면 프로그램을 해당 함수가 작동할 때 throws 뒤에 명시된 예외에 대해 인지하고 있는 것이다. 보통 함수안에서 try catch문을 작성할 경우, 함수 전체 코드를 try로 잡는 것보다 throws로 명시하여 예외 처리를 하는 것이 보기에도 더 클린한 코드가 될 수 있기 때문에 많이 사용한다.
아래와 같이 throws를 사용할 수 있다
void methodName() throws ExceptionType1, ExceptionType2, ... {
// 메서드 내용
}
아래는 divide 메서드에서 ArithmeticException을 발생시키고, 이를 호출한 곳에서 처리하도록 throws를 사용하는 예시 코드이다.
public class Example {
public static void main(String[] args) {
try {
divide(10, 0);
} catch (Exception e) {
System.out.println("0이 아닌 숫자이어야 합니다.");
}
}
public static void divide(int dividend, int divisor) throws ArithmeticException, NullPointerException {
if (divisor == 0) {
throw new ArithmeticException("0으로 나눌 수 없습니다.");
}
if (dividend == 0) {
throw new NullPointerException("나눠지는 수가 0입니다.");
}
int result = dividend / divisor;
System.out.println("나눈 결과: " + result);
}
}
위 코드에서 divide 메서드에서 ArithmeticException을 발생시키며, 이를 throws를 사용하여 호출한 곳으로 넘겨준다. 그리고 호출한 곳에서 이를 catch하여 처리한다.
throws를 사용하여 예외처리를 할 경우 두 가지의 주의사항을 숙지하고 있어야 한다.
throws를 사용하여 예외를 던지는 경우, 해당 예외를 처리하는 try-catch 블록이나, 호출한 메서드의 선언부에 throws 절을 추가해야 한다.
호출한 메서드에서 발생할 수 있는 예외를 적절히 처리해주어야 한다. 그렇지 않으면 컴파일 오류가 발생할 수 있다.
Throwable (java.lang)
|
+-----+-----+
| |
Exception Error
|
+------+------+---------+
| | |
RuntimeException IOException ...
|
+---+----+
| |
NullPointerException ...
자바에서의 모든 예외 클래스는 java.lang.Exception 클래스의 하위 클래스이다. 위와 같은 트리의 구조로 볼 수 있으며 예외처리 단계는 Exception에서 모두 이루어진다.
자바에서는 기본적으로 제공되는 예외 클래스 외에도 사용자가 직접 예외 클래스를 정의하여 만들 수 있다. 이를 통해 특정한 상황에 대응하는 사용자 정의 예외를 만들어 예외 처리를 보다 유연하게 할 수 있다.
사용자 정의 예외 클래스를 만들기 위해서는 Exception 클래스를 상속받아 새로운 예외 클래스를 정의하면 된다.
public class MyCustomException extends Exception {
public MyCustomException() {
super();
}
public MyCustomException(String message) {
super(message);
}
}
위 코드에서 MyCustomException 클래스는 Exception 클래스를 상속받아 사용자 정의 예외를 만들었다. 생성자를 통해 예외 메시지를 전달할 수 있다.
사용자 정의 예외를 사용하기 위해서는 해당 예외를 필요한 곳에서 발생시키고, 호출하는 쪽에서 예외를 처리하면 된다.
public class CustomExceptionExample {
public static void main(String[] args) {
try {
validateInput(5);
} catch (MyCustomException e) {
System.out.println("사용자 정의 예외 발생: " + e.getMessage());
}
}
public static void validateInput(int value) throws MyCustomException {
if (value < 10) {
throw new MyCustomException("입력 값이 유효하지 않습니다.");
}
}
}
위 코드에서 validateInput 메서드는 입력 값이 10보다 작을 경우 MyCustomException을 발생시킨다. 이를 try-catch 블록을 통해 호출한 곳에서 처리한다.
자바를 사용하면서 개발을 하는 도중, 예외처리에 대해 제대로 파악하고자 글을 작성해보았다. 개발과 다양한 테스트를 진행하며 예외 처리에 대해 최대한 자세하고 빠짐없이 작성하려고 하지만 막상 개발하고 뒤돌아보면 또다른 예외가 발생할 수도 있기 때문에 항상 예외에 대해서는 긴장감을 잃지 말아야 할 것이다.