에러와 예외
프로그램이 실행 중 어떤 원인에 의해 오작동을 하거나 비정상적으로 종료되는 경우가 있는데, 이러한 결과를 초래하는 원인을 프로그램 에러 또는 오류라고 한다. 자바에서는 실행 시 발생할 수 있는 프로그램 오류를 에러(error)와 예외(exception) 두 가지로 구분한다.
에러는 메모리 부족(OutOfMemoryError)나 스택오버플로우(StackOverflowError)와 같이 일단 발생하면 복구할 수 없는 심각한 오류이고, 예외는 발생하더라도 수습될 수 있는 비교적 덜 심각한 문제이다. 에러가 발생하면 프로그램의 비정상적인 종료를 막을 수 없지만, 예외는 발생하더라도 적절한 코드를 미리 작성함으로써 비정상적인 종료를 막을 수 있다.
- 에러(Error): 프로그램 코드에 의해서 수습될 수 없는 심각한 오류, 에러는 크게 다음과 같이 3가지로 나뉜다.
- 컴파일 에러: 컴파일 시에 발생하는 에러
- 런타임 에러: 실행 시에 발생하는 에러
- 논리적 에러: 실행은 되지만, 의도와 다르게 동작하는 것
- 예외(Exception): 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류
예외 계층 구조
자바에서는 실행 시 발생할 수 있는 오류를 클래스로 정의했다. 앞서 배운 것처럼 모든 클래스의 조상은 Object클래스이므로 Exception과 Error클래스 역시 Object클래스의 자손들이다.
예외 클래스들은 다음과 같이 두 그룹으로 나눠질 수 있다.
- Exception클래스와 그 자손들
- 프로그램 사용자의 실수와 같은 외적인 요인에 의해 발생하는 예외
- FileNotFountException, ClassNotFoundException, DataFormatException 등
- Runtime Exception클래스와 그 자손들
- 프로그래머의 실수로 발생하는 예외
- ArrayIndexOutOfBoundsException, NullPointerException, ClassCastException, ArithmeticException 등
예외 처리 방법
예외처리(Exception Handling)이란 프로그램 실행 시 발생할 수 있는 예기치 못한 예외의 발생에 대비한 코드를 작성하는 것이다. 발생한 예외를 처리하지 못하면 프로그램은 비정상적으로 종료되며, 처리되지 못한 예외(uncaught exception)는 JVM의 예외처리기(UncaughtExceptionHandler)가 받아서 원인을 화면에 출력한다.
try-catch문
예외를 처리하기 위해선 try-catch문을 사용하며, 그 구조는 다음과 같다.
public class ErrorHandling {
public static void main(String[] args) {
try {
// 예외가 발생할 가능성이 있는 문장들
} catch (Exception1 e1) {
// Exception1이 발생했을 경우, 이를 처리하기 위한 문장들
} catch (Exception2 e2) {
// Exception2이 발생했을 경우, 이를 처리하기 위한 문장들
} catch (Exception3 e3) {
// Exception3이 발생했을 경우, 이를 처리하기 위한 문장들
}
}
// 예외가 발생하지 않으면 catch문을 거치지 않고 try-catch문을 빠져나가 수행을 계속한다.
}
하나의 try블럭 다음에는 여러 종류의 예외를 처리할 수 있도록 하나 이상의 catch블럭이 올 수 있으며, 이 중 발생한 예외의 종류와 일치하는 단 한개의 catch블럭만 수행된다. 발생한 예외의 종류와 일치하는 catch블럭이 없으면 예외는 처리되지 않는다.
class ExceptionEx1 {
public static void main(String[] args) {
try {
try { } catch (Exception e) { }
} catch (Exception e) {
try { } catch (Exception e) { } // 에러, 변수 e가 중복 선언
}
try {
} catch (Exception e) {
}
}
}
위의 예제를 보자. 하나의 메소드 내에 여러 try-catch문을 사용하거나, try 또는 catch 블럭 내에 try-catch문을 중첩해서 사용하는 것이 가능하다.
그러나 catch블럭 내에 또 다른 try-catch문이 포함된 경우, 같은 이름의 참조변수를 사용하면 안된다. 이는 각 catch블럭에 선언된 두 참조변수의 영역이 서로 겹치므로 다른 이름을 사용해야만 서로 구별되기 때문이다. 따라서 위 예제에서 에러가 발생하는 부분의 변수를 'e'가 아닌 다른 것으로 바꿔야 한다.
예외가 발생하면 발생한 예외에 해당하는 클래스의 인스턴스가 만들어진다. 그 후 catch블럭의 괄호 내에서 선언된 참조변수의 종류와 생성된 예외클래스의 인스턴스에 instanceof연산자를 이용해 검사하며 결과가 true인 catch블럭을 만날 때까지 검사가 계속된다.
class ExceptionEx2 {
public static void main(String[] args) {
System.out.println(1);
System.out.println(2);
try {
System.out.println(3);
System.out.println(0 / 0); // 여기서 ArithmeticException 인스턴스 생성
System.out.println(4);
} catch (ArithmeticException ae) {
// instanceof 연산 결과 true
System.out.println("Catch Error!");
}
System.out.println(6);
}
}
// Output
1
2
3
Catch Error!
6
모든 예외 클래스는 Exception클래스의 자손이므로 catch블럭의 괄호에 Exception클래스 타입의 참조변수를 선언해놓으면 어떤 종류의 예외가 발생하더라도 이 catch블럭에 의해 처리된다.
class ExceptionEx3 {
public static void main(String[] args) {
System.out.println(1);
System.out.println(2);
try {
System.out.println(3);
System.out.println(0 / 0); // 여기서 ArithmeticException 인스턴스 생성
System.out.println(4);
} catch (Exception e) {
// Exception클래스가 ArithmeticException클래스의 조상이므로
System.out.println("Catch Error!");
}
System.out.println(6);
}
}
// Output
1
2
3
Catch Error!
6
다음 예제는 위 코드를 살짝 수정했다. 첫 번째 검사에서 일치하는 catch블럭을 찾았기 때문에 두 번째 catch블럭은 검사하지 않게 된다. 만약 try블럭 내에서 ArithmeticException이 아닌 다른 종류의 예외가 발생한 경우엔 두 번째 catch블럭인 Exception클래스 타입의 참조변수를 선언한 곳에서 처리되었을 것이다.
class ExceptionEx4 {
public static void main(String[] args) {
System.out.println(1);
System.out.println(2);
try {
System.out.println(3);
System.out.println(0 / 0);
System.out.println(4);
} catch (ArithmeticException ae) {
if (ae instanceof ArithmeticException) {
System.out.println("true");
}
System.out.println("ArithmeticException");
} catch (Exception e) {
System.out.println("Exception");
}
System.out.println(6);
}
}
// Output
1
2
3
true
ArithmeticException
6
throw
키워드를 throw를 이용하면 고의로 예외를 발생시킬 수 있다.
1. 먼저 연산자 new를 이용해서 발생시키려는 예외 클래스의 객체를 만든다.
Exception e = new Exception("메세지");
2. 그 후 키워드 throw를 이용해서 예외를 발생시킨다.
throw e;
3. 1번과 2번을 한 번에 합쳐서 다음과 같이 쓸 수 있다.
throw new Exception("메세지);
Exception인스턴스를 생성할 때, 생성자에 String을 넣어주면, 이 String이 Exception인스턴스에 메세지로 저장된다. 이 메시지는 getMessage( )를 이용해서 얻을 수 있다.
throws
예외를 처리하는 방법엔 try-catch문을 사용하는 것 외에 throws 키워드를 이용해 예외를 메소드에 선언하는 방법이 있다.
void method() throws Exception1, Exception2, .. Exception N {
// ...
}
다음과 같이 메소드 내에서 발생할 수 있는 예외를 메소드에 선언부에 명시하면 이 메소드를 사용하는 쪽에서는 이에 대한 처리를 하도록 강요하는 효과가 있다.
사실 이러한 방식은 예외를 메소드의 throws에 명시하는 것은 예외를 처리하는 것이 아니라, 자신을 호출한 메소드에게 예외를 전달하여 예외처리를 떠맡기는 것이다.
동일하게 예외를 전달받은 메소드가 또다시 자신을 호출한 메소드에게 전달할 수 있으며, 이런 식으로 계쏙 호출스택에 있는 메소드들을 따라 전달되다가 제일 마지막에 있는 main메소드에서도 예외가 처리되지 않으면 main메소드 마저 종료되어 프로그램 전체가 종료된다.
실제 예제를 통해 살펴보자. 호출된 method1에서 예외가 발생했지만, main메소드 내에서 예외가 처리되는 것을 확인할 수 있다.
class ExceptionEx5 {
public static void main(String[] args) {
try {
method1();
} catch (Exception e) {
System.out.println("main메소드에서 예외가 처리되었습니다.");
}
}
static void method1() throws Exception {
throw new Exception();
}
}
아래 메소드에서 throws키워드를 지우면 다음과 같은 에러메시지가 출력되는 것을 확인할 수 있다.
java: unreported exception java.lang.Exception; must be caught or declared to be thrown
finally블럭
finally블럭은 try-catch문과 함께 예외의 발생여부에 상관없이 실행되어야 할 코드를 포함시킬 목적으로 사용된다. try-catch문의 끝에 선택적으로 덧붙여 사용할 수 있으며, try-catch-finally의 순서로 구성된다.
예외가 발생한 경우엔 try -> catch -> finally의 순으로 실행되며, 예외가 발생하지 않는다면 try -> finally의 순으로 실행된다.
public class ErrorHandling {
public static void main(String[] args) {
try {
// 예외가 발생할 가능성이 있는 문장들
} catch (Exception e) {
// 예외 처리를 위한 문장들
} finally {
// 예외ㅡ 발생여부에 관계없이 항상 수행되어야 하는 문장들
// finally블럭은 try-catch문의 맨 마지막에 위치해야 한다.
}
}
}
위의 5번 예제를 수정한 예제이다. finally 블럭 내 문장이 실행되는 것을 확인할 수 있다.
class ExceptionEx6 {
public static void main(String[] args) {
try {
method1();
} catch (Exception e) {
System.out.println("main메소드에서 예외가 처리되었습니다.");
} finally {
System.out.println("finally블럭 내 문장이 실행되었습니다.");
}
}
static void method1() throws Exception {
throw new Exception();
}
}
// Output
main메소드에서 예외가 처리되었습니다.
finally블럭 내 문장이 실행되었습니다.
'프로그래밍 언어 > Java + Kotlin' 카테고리의 다른 글
[Java] 스터디 11주차: Enum (0) | 2021.08.18 |
---|---|
[Java] 스터디 10주차: 쓰레드 (0) | 2021.08.17 |
[Java] 스터디 8주차: 인터페이스 (0) | 2021.07.22 |
[Java] 스터디 7주차: 패키지 (0) | 2021.07.15 |
[Java] 스터디 6주차: 상속 (0) | 2021.07.07 |