| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 메모리관리
- 자바
- 코딩공부
- 코딩테스트준비
- 자바프로그래밍
- 자료구조
- 개발공부
- 파이썬
- 프로그래머스
- 정렬
- 개발자취업
- JVM
- 프로그래밍기초
- 가비지컬렉션
- 자바공부
- 개발자팁
- 알고리즘공부
- HashMap
- 백준
- 객체지향
- 예외처리
- Java
- 알고리즘
- 클린코드
- 자바개발
- 코딩테스트팁
- 멀티스레드
- 자바기초
- 코딩테스트
- 코딩인터뷰
- Today
- Total
코드 한 줄의 기록
Java 예외 처리 마스터하기: 체크/언체크 예외부터 throw/throws까지 완전 정복 본문
안녕하세요! 오늘은 Java 개발자라면 꼭 알아야 할 예외 처리에 대해 이야기해보려고 합니다. 사실 저도 처음 Java를 배울 때 예외 처리가 참 헷갈렸는데요, 이번 기회에 다시 한번 정리하면서 여러분께도 도움이 될 수 있는 내용을 공유하고 싶습니다.
예외란 무엇인가?
프로그램을 개발하다 보면 예상치 못한 상황들을 많이 마주치게 됩니다. 파일이 존재하지 않거나, 배열의 범위를 초과하거나, null 객체에 접근하려 할 때 등 말이죠. Java에서는 이런 상황들을 예외(Exception)라고 부르며, 체계적으로 관리할 수 있는 메커니즘을 제공합니다.
Java 예외 계층 구조 이해하기
Java의 모든 예외와 에러는 Object 클래스를 최상위로 하는 계층 구조를 가지고 있습니다. 이 구조를 이해하는 것이 예외 처리의 첫걸음입니다.
Object
└── Throwable
├── Error
│ ├── OutOfMemoryError
│ └── StackOverflowError
└── Exception
├── RuntimeException (언체크 예외)
│ ├── NullPointerException
│ ├── ArrayIndexOutOfBoundsException
│ └── IllegalArgumentException
└── IOException (체크 예외)
├── FileNotFoundException
└── SQLException
Throwable 클래스
Throwable은 모든 예외와 에러의 최상위 클래스입니다. throw나 throws 키워드로 던질 수 있는 객체는 반드시 Throwable을 상속받아야 합니다. 이 클래스에는 예외 정보를 확인할 수 있는 중요한 메서드들이 있습니다.
getMessage(): 예외에 대한 상세 메시지를 반환printStackTrace(): 예외 발생 위치와 호출 스택을 출력toString(): 예외에 대한 간략한 정보를 반환
Error vs Exception
Error는 메모리 부족이나 스택 오버플로우 같은 심각한 시스템 오류를 나타냅니다. 이런 에러는 애플리케이션에서 복구할 수 없으므로, 개발자가 직접 처리하려고 하면 안 됩니다.
Exception은 프로그래머의 실수나 사용자의 잘못된 입력 등으로 발생하는 예외로, 적절한 처리를 통해 프로그램의 정상 동작을 유지할 수 있습니다.
체크 예외 vs 언체크 예외
Java의 예외는 컴파일러가 처리를 강제하는지에 따라 체크 예외와 언체크 예외로 구분됩니다.
체크 예외 (Checked Exception)
체크 예외는 컴파일 시점에 반드시 처리해야 하는 예외입니다. RuntimeException을 상속받지 않은 Exception의 하위 클래스들이 여기에 속합니다.
- 컴파일러가 예외 처리를 강제함
- 복구 가능성이 있는 예외 상황
- try-catch로 처리하거나 throws로 선언해야 함
- 외부 요인에 의해 발생 (파일 입출력, 네트워크 통신 등)
대표적인 체크 예외들
IOException: 파일 입출력 작업 중 발생하는 예외SQLException: 데이터베이스 작업 중 발생하는 예외FileNotFoundException: 존재하지 않는 파일에 접근할 때 발생ClassNotFoundException: 클래스를 찾을 수 없을 때 발생
// FileNotFoundException 처리 예시
try {
FileInputStream file = new FileInputStream("test.txt");
// 파일 읽기 작업
} catch (FileNotFoundException e) {
System.out.println("파일을 찾을 수 없습니다: " + e.getMessage());
} catch (IOException e) {
System.out.println("파일 읽기 중 오류가 발생했습니다: " + e.getMessage());
}
언체크 예외 (Unchecked Exception)
언체크 예외는 컴파일 시점에 처리를 강제하지 않는 예외로, RuntimeException과 그 하위 클래스들이 여기에 속합니다.
- 컴파일러가 예외 처리를 강제하지 않음
- 주로 프로그래머의 실수로 발생
- 런타임 시점에 발생
- 예외 처리는 개발자의 선택
대표적인 언체크 예외들
NullPointerException: null 객체에 접근할 때 발생ArrayIndexOutOfBoundsException: 배열 범위를 초과할 때 발생IllegalArgumentException: 잘못된 매개변수를 전달할 때 발생ArithmeticException: 산술 연산 오류 (예: 0으로 나누기)
// NullPointerException 예방 예시
String str = null;
// 위험한 방법
// System.out.println(str.length()); // NullPointerException 발생!
// 안전한 방법
if (str != null) {
System.out.println(str.length());
} else {
System.out.println("문자열이 null입니다.");
}
throw vs throws 완벽 이해하기
많은 개발자들이 헷갈려 하는 부분이 바로 throw와 throws의 차이입니다. 이름이 비슷해서 더 헷갈리는데, 사용법과 목적이 완전히 다릅니다.
throw: 예외를 직접 발생시키기
throw는 프로그래머가 의도적으로 예외를 발생시킬 때 사용합니다.
public void validateAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("나이는 음수가 될 수 없습니다.");
}
if (age > 150) {
throw new IllegalArgumentException("현실적이지 않은 나이입니다.");
}
}
throws: 예외를 상위 메서드로 위임하기
throws는 메서드가 특정 예외를 던질 수 있음을 선언하여, 호출하는 쪽에서 예외를 처리하도록 위임합니다.
public void readFile(String fileName) throws IOException, FileNotFoundException {
FileInputStream file = new FileInputStream(fileName);
// 파일 읽기 작업
// IOException이나 FileNotFoundException이 발생할 수 있음을 선언
}
public void processFile() {
try {
readFile("data.txt");
} catch (FileNotFoundException e) {
System.out.println("파일을 찾을 수 없습니다.");
} catch (IOException e) {
System.out.println("파일 처리 중 오류가 발생했습니다.");
}
}
throw와 throws 함께 사용하기
public void processData(String data) throws CustomException {
if (data == null || data.isEmpty()) {
throw new CustomException("데이터가 비어있습니다."); // throw로 예외 발생
}
// 데이터 처리 로직
}
예외 처리 방법: try-catch-finally
예외가 발생했을 때 이를 처리하는 가장 기본적인 방법이 try-catch-finally 구문입니다.
기본 구조
try {
// 예외가 발생할 수 있는 코드
} catch (SpecificException e) {
// 특정 예외 처리
} catch (AnotherException e) {
// 다른 예외 처리
} finally {
// 예외 발생 여부와 관계없이 항상 실행되는 코드
}
실제 사용 예시
public class FileProcessor {
public void processFile(String fileName) {
FileInputStream file = null;
try {
file = new FileInputStream(fileName);
// 파일 처리 로직
System.out.println("파일 처리 완료");
} catch (FileNotFoundException e) {
System.err.println("파일을 찾을 수 없습니다: " + fileName);
e.printStackTrace();
} catch (IOException e) {
System.err.println("파일 읽기 중 오류 발생: " + e.getMessage());
} catch (Exception e) {
System.err.println("예상치 못한 오류 발생: " + e.getMessage());
} finally {
// 리소스 정리
if (file != null) {
try {
file.close();
} catch (IOException e) {
System.err.println("파일 닫기 중 오류 발생");
}
}
}
}
}
try-with-resources: 더 깔끔한 리소스 관리
Java 7부터는 try-with-resources 구문을 사용하여 더 간편하게 리소스를 관리할 수 있습니다.
public void readFileContent(String fileName) {
try (FileInputStream file = new FileInputStream(fileName);
BufferedReader reader = new BufferedReader(new InputStreamReader(file))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.err.println("파일 처리 중 오류 발생: " + e.getMessage());
}
// 자동으로 리소스가 닫힘
}
실전 예외 처리 전략
1. 구체적인 예외 잡기
일반적인 Exception보다는 구체적인 예외를 잡는 것이 좋습니다.
// 좋지 않은 방법
try {
// 코드
} catch (Exception e) {
// 너무 광범위한 예외 처리
}
// 좋은 방법
try {
// 코드
} catch (FileNotFoundException e) {
// 파일을 찾을 수 없는 경우의 구체적인 처리
} catch (IOException e) {
// 입출력 오류에 대한 처리
}
2. 의미 있는 예외 메시지 작성
public void transferMoney(String fromAccount, String toAccount, double amount) {
if (amount <= 0) {
throw new IllegalArgumentException(
String.format("이체 금액이 잘못되었습니다. 금액: %.2f", amount)
);
}
if (fromAccount == null || fromAccount.isEmpty()) {
throw new IllegalArgumentException("출금 계좌가 지정되지 않았습니다.");
}
// 이체 로직
}
3. 예외 복구 전략
때로는 예외가 발생했을 때 다른 방법으로 문제를 해결할 수 있습니다.
public String getConfigValue(String key) {
try {
return readFromFile(key);
} catch (IOException e) {
// 파일 읽기 실패 시 기본값 반환
System.out.println("설정 파일 읽기 실패, 기본값 사용: " + e.getMessage());
return getDefaultValue(key);
}
}
사용자 정의 예외 만들기
비즈니스 로직에 특화된 예외를 만들어 사용하면 코드의 가독성과 유지보수성이 향상됩니다.
// 사용자 정의 예외 클래스
public class InsufficientBalanceException extends Exception {
private final double currentBalance;
private final double requestedAmount;
public InsufficientBalanceException(double currentBalance, double requestedAmount) {
super(String.format("잔액이 부족합니다. 현재 잔액: %.2f, 요청 금액: %.2f",
currentBalance, requestedAmount));
this.currentBalance = currentBalance;
this.requestedAmount = requestedAmount;
}
public double getCurrentBalance() {
return currentBalance;
}
public double getRequestedAmount() {
return requestedAmount;
}
}
// 사용 예시
public void withdraw(double amount) throws InsufficientBalanceException {
if (amount > balance) {
throw new InsufficientBalanceException(balance, amount);
}
balance -= amount;
}
자주 마주치는 예외들과 해결 방법
NullPointerException 예방하기
가장 흔한 예외 중 하나인 NPE를 예방하는 방법들입니다.
// 방법 1: null 체크
public int getStringLength(String str) {
if (str != null) {
return str.length();
}
return 0;
}
// 방법 2: Optional 사용 (Java 8+)
public Optional findUser(String userId) {
// ... 사용자 검색 로직
return Optional.ofNullable(foundUser);
}
// 방법 3: 방어적 프로그래밍
public boolean isEqual(String str1, String str2) {
return Objects.equals(str1, str2); // null 안전
}
ArrayIndexOutOfBoundsException 예방하기
배열 범위 초과 예외를 예방하는 방법입니다.
public void printArrayElement(int[] array, int index) {
if (array != null && index >= 0 && index < array.length) {
System.out.println(array[index]);
} else {
System.out.println("잘못된 인덱스이거나 배열이 null입니다.");
}
}
// 향상된 for문 사용으로 인덱스 오류 방지
public void printAllElements(int[] array) {
if (array != null) {
for (int element : array) {
System.out.println(element);
}
}
}
Java의 예외 처리는 처음엔 복잡해 보이지만, 체계적으로 이해하고 나면 안정적이고 견고한 프로그램을 작성하는 데 큰 도움이 됩니다.
핵심 포인트를 정리하면
- 체크 예외는 컴파일 시점에 처리가 강제되며, 주로 외부 요인으로 발생합니다
- 언체크 예외는 런타임에 발생하며, 주로 프로그래밍 오류로 인해 발생합니다
- throw는 예외를 직접 발생시키고, throws는 예외 처리를 호출자에게 위임합니다
- try-catch-finally 구문으로 예외를 적절히 처리하고, 가능하면 구체적인 예외를 잡아 처리합니다
- 예외 발생 시 적절한 복구 전략을 수립하고, 의미 있는 에러 메시지를 제공합니다
예외 처리는 단순히 오류를 막는 것이 아니라, 사용자에게 더 나은 경험을 제공하고 시스템의 안정성을 높이는 중요한 개발 기법입니다. 앞으로도 다양한 상황에서 적절한 예외 처리를 통해 더욱 견고한 Java 애플리케이션을 만들어 나가시기 바랍니다!
Java 설계 원칙 완전 정복: 초보자를 위한 SOLID 입문 가이드
객체지향 설계에서 안정성·유연성·확장성을 보장하는 SOLID 원칙은 코드 품질을 높이는 필수 지침이다.이 글에서는 SOLID 각 원칙의 개념과 Java 예제, 그리고 학습 팁을 공유하며 함께 공부할 수
byteandbit.tistory.com
'JAVA' 카테고리의 다른 글
| Java 사용자 정의 예외와 예외 전환·포장 전략 완벽 가이드: 실전 예제와 베스트 프랙티스 (0) | 2025.10.16 |
|---|---|
| Java try-catch-finally와 try-with-resources로 자원 누수 완벽 방지하기 (0) | 2025.10.15 |
| Java 설계 원칙 완전 정복: 초보자를 위한 SOLID 입문 가이드 (0) | 2025.10.13 |
| Java 패키지와 모듈 시스템: 체계적인 코드 구조화 가이드 (0) | 2025.10.12 |
| 자바 equals/hashCode/toString 설계 원칙 총정리 + Lombok 사용 시 주의점까지 한 번에 정리 (0) | 2025.10.12 |