예외 처리란?
프로그램을 실행하다가 보면 어떠한 원인 때문에 비정상적인 동작을 일으키며 프로그램이 종료되는 상황을 보신 적 있으실 겁니다.
에러의 종류는 우리가 컴파일할 때 발생할 수 있는 컴파일 오류와 실행 중 발생하는 런타임 오류 두 종류가 있습니다.
컴파일 오류는 IDE를 통해 잡기 쉬집지만, 런타임 오류는 잡기가 까다롭습니다. 자바에서는 런타임 오류를 두 종류로 보고 있습니다. 바로 에러(Error)와 예외(Exception)입니다.
에러는 프로그램이 코드로 복구할 수 없는 오류를 의미하고 예외는 프로그래머가 직접 예측하여 막을 수 있는 처리 가능한 오류입니다.
아래의 예제 코드를 보도록 하겠습니다
class Test {
public static void main(String[] args){
int a,b;
a= 10;
b= 0;
int c=a/b;
System.out.println(c);
}
}
//출력 결과
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Main.main(Main.java:6)
위 예제를 보면 6 행에서 arithmeticException 예외가 발생함을 볼 수 있습니다.
이는 어느 수를 0으로 나눌 수 없기에 발생하는 전형적인 예외입니다.
이처럼 예외가 발생하여 프로그램이 예기치 않게 종료되는 상황을 막기 위해서 자바에서는 이를 처리하는 try ~ catch구문을 제공하고 있습니다.
이 구문에 대해 알아보기 전에 어떤 예외가 있는지 살펴보도록 하겠습니다.
예외 계층(Exception Hierarchy)
자바에서 예외는 크게 검사 예외(Checked Exception), 비검사 예외(Unchecked Exception)로 나눌 수 있습니다.
Checked Exception
exception 클래스의 자식 클래스이면서 RuntimeException 클래스를 상속받지 않은 모든 예외는 컴파일러가 컴파일 중에 프로그래머가 해당 예외를 처리했는지를 검사하기 때문에 검사 예외 혹은 컴파일 타임 예외라고 합니다.
위의 에러를 해결하기 위해서는 try-catch문으로 예외를 처리하거나, 메서드 선언에 throws 키워드를 사용하여 예외 처리를 다른 곳으로 넘겨야 합니다.
Unchecked Exception
RuntimeException 클래스를 상속받으며, 프로그램 실행 중에 발생하는 예외를 비검사 예외 혹은 런타임 예외라고 합니다.
이 예외는 컴파일 타임이 아니라 런타임에 발생하므로, 검사 예외와는 다르게 프로그램 실행 전 예외 처리를 강제하지는 않습니다.
try~catch
try ~catch 구문은 예외가 발생할 위험이 있는 부분(try)과, 예외가 처리되는 부분(catch)로 나뉩니다. 아래는 try~catch문의 기본 구성입니다.
try{
// 예외가 발생할 위험이 있는 부분
} catch(예외 타입 변수명){
// 예외를 처리하는 부분
}
만약에 try 블록에서 예외가 발생하면 catch의 예외 타입에 해당 예외를 지정 해주었다면, try 블록이 아닌 catch 블록의 코드가 수행됩니다. try 블록에서 여러 종류의 예외가 발생할 수 있는 경우라면, 아래와 같이 catch절을 제한 없이 여러 개 작성할 수 있습니다.
try {
} catch (예외타입1 변수명1) {
} catch (예외타입2 변수명2) {
}
... catch (예외타입n 변수명n) {
}
바로 try ~ catch문을 사용한 예제를 보도록 하겠습니다
pulbic class ExceptionHandlingEx{
public static void main (String[] args){
int a = 10;
int b = 0;
try{ // try
System.out.printf("a / b = ");
int result = a / b; // 10 / 0 으로 예외 발생!!
System.out.println(result); // 위에서 예외 발생하여 catch 구문으로 바로 넘어갔기 때문에 이 코드는 실행되지 않습니다.
} catch (ArithmeticException e) { // 예외 발생으로 catch 구문 수행!
System.out.println("올바르지 않은 나눗셈 연산!!");
}
}
}
// 출력 결과
a / b = 올바르지 않은 나눗셈 연산!!
위의 예제 코드의 결과를 보면 예외 발생시 이전의 예제와 같이 프로그램이 종료되지 않고 catch 블록을 수행함을 알 수 있습니다. 여기서 주의해야 할 점은 try 블록 내에서 예외가 발생하면 남은 코드는 실행치 않고 바로 예외에 해당하는 catch 구문을 수행합니다.
finally
finally절을 사용하면 try 블록에서 예외가 발생하는 것과 상관없이 반드시 실행되는 코드를 작성할 수 있습니다.
try~catch~finally문의 기본 구성은 다음과 같습니다
try {
}catch(예외타입 변수명){
}finally{
}
위의 구성의 실행 순서는
- 예외가 발생 했을 때 : try -> catch -> finally
- 예외가 발생 안 했을 때: try -> finally
아래와 같이 catch절을 제외한 try ~ finally문을 사용할 수도 있습니다
try{
//예외가 발생할 위험이 있는 try 블록
} finally {
// 예외에 상관없이 반드시 수행되는 finally 블록
}
아래 예시 코드를 보도록 하겠습니다.
public class Main {
public static void main(String[] args) {
someMethod();
}
public static void someMethod(){
try{
System.out.println("try 블록");
return;
}finally {
System.out.println("finally 블록");
}
}
}
// 출력 결과
try 블록
finally 블록
결과를 보면 try 블록에 return문을 만나도 finally는 무조건 수행됩니다.
throw
예외를 의도적으로 발생하는 것이 throw입니다.
예제 코드를 함께 보도록 하겠습니다
pulbic class test {
public static void main(String[] args) {
try{
throw new Exception();
// 강제로 Exception 객체를 생성
} catch (Exception e) {
System.out.println("예외를 강제로 발생시켰습니다");
}
}
}
// 출력 결과
예외를 강제로 발생시켰습니다
위의 예제 코드를 보면 throw를 통해 의도적으로 예외를 발생시킨 것을 확인할 수 있습니다.
그렇다면 왜 예외를 의도적으로 발생 시킬까요?
그 이유를 아래 예제 코드와 함께 확인해 보도록 하겠습니다.
class Test {
public void test(String a, String b) throws NumberFormatException {
int sum = Integer.parseInt(a) + Integer.parseInt(b);
System.out.println("문자로 입력받은 수 의 합은 " + sum + "입니다.");
}
}
public class Main {
public static void main(String[] args) {
Test test = new Test();
try{
test.test("1","ㄱ"); // "ㄱ"으로 인해 예외 발생
}catch (NumberFormatException e){
System.out.println("입력하신 값은 숫자가 아닙니다.");
}
}
}
// 출력 결과
입력하신 값은 숫자가 아닙니다.
위 예제 코드를 보면 Test 클래스의 test 메서드는 String 형의 수를 입력받아 합해주는 메서드입니다.
그러나 Main클래스의 test 메서드는 "ㄱ"을 입력받는 모습을 확인할 수 있습니다.
그로 인해서 예외가 발생하고 이를 Main에서 catch 블록으로 예외를 처리한 모습을 볼 수 있습니다.
이는 test 메서드의 throws 키워드를 통해 예외를 Main 클래스로 던져주었기 때문에 할 수 있는 상황입니다.
만약 메서드를 여러 곳에서 만들고, 그 메서드에서 예외가 발생하고, 그 예외를 해결하기 위해서 이들을 수정한다면 그만큼 많은 시간이 소요됩니다.
즉, throws 키워드를 사용하여 예외 처리를 호출한 쪽에서 처리한다는 것입니다.
이와 같이 thorws를 사용하여 메서드 안에서 예외처리를 하지 않고 메서드를 호출한 main에서 예외 처리를 할 수 있습니다.
마지막으로 위에서 언급하였던 throw와 throws를 함께 사용할 수 있는 예제를 살펴보도록 하겠습니다.
class Test {
public void test(String a, String b) throws NumberFormatException {
try {
int sum = Integer.parseInt(a) + Integer.parseInt(b); // 2. 예외 발생으로 catch 문 이동!
System.out.println("문자로 입력받은 수 의 합은 " + sum + "입니다.");
}catch (NumberFormatException e){
System.out.println("숫자형 문자가 아닙니다. 형 변환이 불가능합니다."); // 3. catch 블록 수행!
throw e; 4. NumberFormatException e를 던져줌!
}
}
}
public class Main {
public static void main(String[] args) {
Test test = new Test(); // 1. Test 클래스를 이용해 test 인스턴스 생성
try{
test.test("1","ㄱ");} // 2. 숫자형 문자가 아니여서 예외 발생!
catch (NumberFormatException e){ 5. 위의 메서드에서 예외가 넘어와 수행!
System.out.println("입력하신 값은 숫자가 아닙니다."); 6. catch구문 수행!
}
}
}
//출력 결과
숫자형 문자가 아닙니다. 형 변환이 불가능합니다.
입력하신 값은 숫자가 아닙니다.
Reference
'JAVA' 카테고리의 다른 글
[JAVA] 컬렉션 프레임워크(Collection Framework) - List <E> (2) (0) | 2023.03.09 |
---|---|
[JAVA] 컬렉션 프레임워크(Collection Framework) (1) (0) | 2023.03.09 |
[JAVA] 제네릭(Generic) (0) | 2023.03.07 |
[JAVA] 열거형(enum) (0) | 2023.03.06 |
[JAVA] Windows 개발 환경에서 자바 설치 및 개발 환경 세팅 (0) | 2023.02.20 |