이전 포스팅에서 다뤘던 [ [JAVA] Java에서 예외 처리방법 ] 내용을 참고하시면, 이번 주제에 대한 배경 지식이나 기초 정보를 확인하실 수 있습니다!
1. 🌌 Java의 예외 계층 구조와 개념
Java에서는 프로그램 실행 중 발생할 수 있는 문제를 체계적으로 관리하고 프로그램의 안정성을 높이기 위해 예외 처리를 제공합니다. 모든 예외 상황은 Throwable
이라는 최상위 클래스를 중심으로 Error와 Exception으로 구분되며, 각 예외는 발생하는 시점과 성격에 따라 다르게 처리됩니다.
🔹 Throwable 계층 구조
Throwable
: 모든 예외 객체의 최상위 부모 클래스입니다. 예외 상황을 구분하는 기반이 되며, Error와 Exception의 공통 부모 역할을 합니다.Error
: 주로 JVM 레벨에서 발생하는 치명적 오류로, 개발자가 예측하거나 처리하기 매우 어려운 경우가 많습니다.Exception
: 애플리케이션 수준에서 발생하는 오류로, 적절한 처리를 통해 복구 가능성이 높은 오류입니다.
🧩 JVM 실행 vs 애플리케이션 수준: 개념과 차이점
JVM 실행(JVM Level)은 Java Virtual Machine이 Java 코드를 운영 체제 위에서 실행하고 관리하는 수준을 의미합니다. 이 단계에서 발생하는 오류는 JVM 내부 자원 부족, 메모리 문제, 시스템 하드웨어 문제 등 시스템 전반에 영향을 미치는 심각한 오류가 포함됩니다. JVM은 이처럼 시스템 리소스와 직접 관련된 문제를 다루기 때문에 일반적으로 복구가 불가능합니다.
반면, 애플리케이션 수준(Application Level)은 우리가 작성한 애플리케이션 코드에서 발생하는 예외를 의미합니다. 여기에는 파일 입출력 문제, 데이터베이스 접근 오류, 잘못된 연산 등 로직이나 외부 자원과의 상호작용에서 발생하는 문제가 포함됩니다. 이와 같은 문제는 예외 처리를 통해 애플리케이션이 안정적으로 실행될 수 있도록 복구할 수 있습니다.
2. 🧩 Error와 Exception: 발생 시점과 성격에 따른 차이
Java의 예외는 크게 JVM 실행 수준에서 발생하는 시스템 오류와 애플리케이션 수준에서 발생하는 오류로 구분할 수 있습니다. 이 차이에 따라 Error와 Exception으로 구분됩니다.
1) Error 클래스 - JVM 레벨에서 발생하는 시스템 오류
Error는 대부분 JVM이 Java 코드를 실행하는 중 메모리 부족, 무한 재귀 호출, 시스템 하드웨어 문제 등과 같은 치명적인 문제를 만났을 때 발생합니다. Error는 시스템 차원에서 발생하는 문제이기 때문에 대부분 복구가 불가능하며, try-catch 블록으로 처리하지 않는 것이 일반적입니다.
- 발생 시점: 실행 중 JVM이 심각한 문제를 만났을 때
- 복구 가능성: 대부분 복구 불가능, 단 일부 간접적 처리 가능
- 주요 Error 예시:
OutOfMemoryError
: JVM이 사용할 수 있는 메모리가 부족할 때 발생- 예: 대량의 데이터를 한꺼번에 로드하거나 메모리 누수가 발생하는 경우
- 처리 가능성:
try-catch
로 잡을 수는 있으나, 메모리를 해제하거나 최적화하는 등 근본적인 해결이 필요합니다.
StackOverflowError
: 메서드 호출이 너무 깊어져 스택 메모리가 초과될 때 발생- 예: 재귀 함수의 종료 조건이 잘못되어 무한히 재귀 호출이 이루어질 때
- 처리 가능성: 종료 조건을 설정하여 무한 재귀 호출을 방지하고, 코드 자체를 수정하여 예방할 수 있습니다.
InternalError
: JVM 내부에서 예기치 않은 문제가 발생할 때 발생- 예: JVM 실행 중 예상하지 못한 하드웨어나 운영 체제의 문제가 발생한 경우
- 처리 가능성: 발생 원인이 JVM 내부 문제이기 때문에 애플리케이션 수준에서는 해결이 어렵습니다.
AssertionError
: 코드 검증을 위해 사용된assert
구문이 실패할 때 발생- 예: 특정 조건이 거짓으로 판명될 때
assert
로 예외 상황을 확인하고 개발자가 이를 검토할 수 있습니다. - 처리 가능성: 주로 디버깅 과정에서 사용되며,
assert
구문을 통해 테스트 중인 조건이 만족되지 않을 때 발생합니다.
- 예: 특정 조건이 거짓으로 판명될 때
2) Exception 클래스 - 애플리케이션 레벨에서 발생하는 오류
Exception은 애플리케이션이 동작 중 발생할 수 있는 오류를 의미하며, 보통 적절한 예외 처리를 통해 복구 가능합니다. Exception은 다시 Checked Exception과 Unchecked Exception (Runtime Exception)으로 나뉩니다. 각각의 예외는 발생 시점과 상황에 따라 다르게 처리됩니다.
3. 📜 Checked Exception vs Unchecked Exception: 예외 발생 시점과 구체적인 예시
1) Checked Exception (컴파일 시점에서 발생)
Checked Exception은 외부 자원과의 상호작용 중 발생할 가능성이 높은 예외로, 예외 처리가 강제됩니다. 컴파일러는 Checked Exception이 발생할 가능성이 있는 코드를 try-catch
로 감싸거나 throws
로 선언하도록 요구합니다. 만약 이를 처리하지 않으면 컴파일 오류가 발생합니다.
- 발생 시점: 파일 입출력, 데이터베이스 접근, 네트워크 연결 등 외부 자원 접근 시 발생
- 복구 가능성: 대부분 복구 가능, 예외 처리를 통해 프로그램이 안정적으로 유지될 수 있습니다.
- 주요 Checked Exception 예시:
IOException
: 파일 입출력 작업에서 발생하는 예외- 예: 읽으려는 파일이 존재하지 않거나, 접근 권한이 없는 경우 발생
SQLException
: 데이터베이스와 연결 중 발생하는 예외- 예: 데이터베이스 연결 오류, SQL 구문 오류 등
ClassNotFoundException
: 지정된 클래스가 존재하지 않을 때 발생- 예:
Class.forName("ClassName")
으로 클래스를 찾으려 했으나 클래스가 존재하지 않는 경우
- 예:
InterruptedException
: 스레드가 실행을 대기하는 중 인터럽트될 때 발생- 예:
Thread.sleep()
중에 다른 스레드가 인터럽트를 발생시키는 경우
- 예:
2) Unchecked Exception (Runtime Exception, 실행 시점에서 발생)
Unchecked Exception, 즉 RuntimeException은 실행 중 발생하는 예외로, 컴파일러가 예외 처리를 강제하지 않습니다. 주로 프로그래밍 논리 오류나 잘못된 접근으로 인해 발생하며, 개발자가 코드 자체를 수정하여 해결할 수 있습니다.
- 발생 시점: 코드의 논리 오류, 잘못된 접근 또는 연산으로 인해 발생
- 복구 가능성: 코드 수정으로 해결 가능
- 주요 Unchecked Exception 예시:
NullPointerException
:null
참조가 발생했을 때- 예: 객체가 초기화되지 않은 상태에서 해당 객체의 메서드나 필드에 접근할 때 발생
ArrayIndexOutOfBoundsException
: 배열의 유효하지 않은 인덱스에 접근할 때- 예: 배열 크기를 초과하는 인덱스를 참조하려 할 때
ArithmeticException
: 잘못된 산술 연산을 시도할 때- 예: 0으로 나누기를 시도하는 경우
IllegalArgumentException
: 메서드에 부적절한 인수가 전달될 때- 예: 특정 메서드에 예상한 값이 아닌 다른 값이 전달되는 경우
ClassCastException
: 잘못된 타입으로 객체를 캐스팅할 때- 예: 실제 타입과 맞지 않는 타입으로 강제 형 변환을 시도할 때
3) Unchecked Exception과 RuntimeException의 관계
Java에서 RuntimeException은 모든 Unchecked Exception의 상위 클래스입니다. RuntimeException
을 상속하는 예외 클래스들은 모두 Unchecked Exception에 해당하므로, RuntimeException
으로 선언하면 모든 Unchecked Exception을 포괄할 수 있습니다.
🔹 RuntimeException이 포괄하는 예외들
- NullPointerException: null 참조가 발생할 때
- ArrayIndexOutOfBoundsException: 배열의 유효하지 않은 인덱스를 참조할 때
- ArithmeticException: 잘못된 산술 연산(예: 0으로 나누기)을 시도할 때
- IllegalArgumentException: 메서드에 부적절한 인수가 전달될 때
- ClassCastException: 잘못된 타입으로 객체를 캐스팅할 때
이처럼 모든 Unchecked Exception은 RuntimeException을 상속받아 이루어지기 때문에 catch (RuntimeException e)
구문으로 Unchecked Exception을 포괄적으로 처리할 수 있습니다.
4) Checked Exception을 포괄하는 상위 클래스는 존재하지 않음
Checked Exception은 컴파일러가 예외 처리를 강제하는 예외들이기 때문에, Checked Exception 전체를 포괄하는 상위 클래스는 없습니다. Java는 Checked Exception을 반드시 구체적으로 명시하도록 설계했기 때문에, Checked Exception의 최상위 클래스를 만들지 않았습니다.
🔹 왜 Checked Exception은 포괄하는 클래스가 없을까?
Java는 Checked Exception이 발생할 가능성이 높은 코드에서 예외를 명확하게 처리하도록 강제하는 언어입니다. 예를 들어, 파일 입출력, 데이터베이스 접근, 네트워크 연결 등에서 발생할 수 있는 예외들은 각각 IOException
, SQLException
등으로 구체화되어 있고, 개발자는 특정 예외에 맞는 처리를 하도록 요구받습니다. 이를 통해 코드의 안정성과 예외 처리의 구체성을 높일 수 있죠.
즉, 모든 Checked Exception을 한꺼번에 잡아 처리할 수 있는 상위 클래스는 없으며, 개발자가 필요한 Checked Exception만 구체적으로 처리하도록 디자인되어 있습니다.
5) 🧩 모든 예외를 포괄하고 싶을 때: Exception 사용
모든 예외 상황을 한 번에 포괄하고 싶을 때는 Exception 클래스를 사용할 수 있습니다. Exception 클래스는 Throwable
하위의 모든 Checked Exception과 Unchecked Exception의 부모 클래스이므로, 모든 예외를 포괄적으로 처리할 수 있습니다.
try {
// 예외가 발생할 가능성이 있는 코드
} catch (Exception e) {
// 모든 Checked 및 Unchecked 예외를 포괄하는 처리
}
이 방식은 구체적인 예외 처리의 장점을 잃어버릴 수 있으므로 주의해서 사용해야 합니다. 상황에 맞는 예외를 구체적으로 처리하는 것이 보통 더 권장됩니다.
4. 🧭 예외 처리 방법: try-catch-finally, throw, throws
Java에서는 예외를 효율적으로 관리하기 위해 다양한 예외 처리 구문을 제공합니다. 각 구문에 대해 살펴보겠습니다.
🔹 try-catch-finally 구문
try-catch
구문은 예외가 발생할 가능성이 있는 코드를 try
블록에 작성하고, 특정 예외가 발생했을 때 catch
블록에서 이를 처리하는 기본 구문입니다. finally
블록은 예외 발생 여부와 관계없이 항상 실행되며, 주로 리소스를 정리하는 데 사용됩니다.
try {
// 예외 발생 가능성이 있는 코드
} catch (ExceptionType e) {
// 예외 발생 시 처리 코드
} finally {
// 예외 발생 여부와 관계없이 항상 실행됨
}
🔹 throw와 throws 키워드
throw
: 특정 예외를 강제로 발생시킬 때 사용합니다. 예를 들어, 잘못된 사용자 입력값이 들어왔을 때throw new IllegalArgumentException("Invalid input")
과 같이 사용할 수 있습니다.throws
: 메서드 선언부에서 사용되며, 메서드가 처리하지 않고 호출한 쪽으로 예외를 전달할 때 사용합니다.
public void someMethod() throws IOException {
// 메서드 내에서 IOException 발생 가능성 있음
}
5. 🎯 예외 처리의 중요성: 프로그램 안정성 및 유지보수성 강화
Java의 예외 처리는 단순히 오류를 해결하기 위한 도구가 아니라, 프로그램이 예기치 않은 상황에서도 안정적으로 동작할 수 있도록 합니다. 예외 처리를 통해 외부 자원 문제, 사용자 입력 오류 등을 효과적으로 관리하여 시스템 다운타임을 최소화할 수 있습니다.
Java 예외 처리 요약
Error: 시스템 수준에서 발생하며, 보통 복구가 불가능합니다.
Checked Exception: 컴파일 시점에서 발생 가능성이 감지되며, 예외 처리가 강제됩니다.
Unchecked Exception (Runtime Exception): 실행 시점에 발생하며, 개발자가 코드 자체를 수정해 해결할 수 있습니다.
try-catch-finally 구문을 사용해 예외를 효과적으로 관리하고 throw와 throws로 예외를 전달하거나 강제로 발생시킬 수 있습니다.
위의 내용으로 Java 예외 처리의 개념, 구조, 종류와 처리 방법을 종합적으로 설명해드렸습니다. 😊 각 예외 유형과 그에 따른 처리를 통해 더욱 안정적인 프로그램을 작성할 수 있습니다!
'Java' 카테고리의 다른 글
[JAVA] Spring 공통 모듈을 패키지화하고 GitHub Packages에 등록하는 방법 (0) | 2024.12.06 |
---|---|
[JAVA] Java로 Excel 파일 생성 및 관리: Apache POI 사용법 (0) | 2024.11.26 |
[JAVA] Java에서 예외 처리방법 (7) | 2024.11.13 |
[JAVA] 프로젝트에서 PMD를 이용한 소스 품질 검사 (0) | 2024.11.11 |
[JAVA] Spring 돌아보기 Part.3 #JDBC #MyBatis #Multiple DataSources (4) | 2024.10.10 |