데이터 입/출력, 네트워크 통신, 데이터 통신, 외부 api 호출 등 이러한 예측하기 힘든 외부와 상호작용할 때는 예외를 처리해주어야 한다.
프로그래밍을 하는 과정에서 실수로 인해 예외가 발생할 수도 있다. 물론 이는 최대한 예방하는 게 좋다.
데이터 무결성 위반, 권한 부족, 재고 부족 등으로 더이상 정상적인 처리가 발생했을 때 예외를 처리한다.
사용자 입력이 잘못되었을 때도 발생할 수 있다. 예를 들어 조건에 맞지 않는 문장이나 숫자를 넣거나 돈이 없는데도 출금을 시도하는 경우 등 이럴 때는 예외를 처리해주어야 한다. 그리고 프로그램의 설정 파일이 유효하지 않을 떄도 처리해주어야 한다.
예외 처리는 주로 외부와 상호작용하거나 잘못된 입력이나 실수로 프로그램이 더이상 정상적인 처리를 할 수 없을 때 발생할 때 사용해야 한다. 혹은 복구 로직이 확실히 있거나 사용자에게 예외가 발생했다는 걸 알릴 때도 사용할 수 있다.
요새는 checked Exception도 unchecked Exception으로 씌워서 처리하는 경우가 많다. 물론 명확히 처리할 수 있으면 checked Exception을 처리해도 되지만, 씌우는 쪽이 좀 더 중앙화하여 처리할 수 있고 코드가 좀 더 간결해진다. 게다가 checked Exception인 경우 과도한 try-catch 블록, api 변경이 어려워짐, 낮은 복구 가능성이라는 단점이 있기 때문에 이를 피하려고 씌우는 경우가 많다.
import java.io.FileReader;
import java.io.IOException;
public class ExceptionHandlingExample {
public static void main(String[] args) {
// 1. try-catch 블록을 사용한 예외 처리
try {
int result = 10 / 0; // ArithmeticException 발생
System.out.println("Result: " + result); // 이 코드는 실행되지 않음
} catch (ArithmeticException e) {
System.err.println("0으로 나눌 수 없습니다: " + e.getMessage());
} catch (Exception e) { // 더 일반적인 예외는 아래쪽에 작성 (다형성)
System.err.println("예상치 못한 오류가 발생했습니다: " + e.getMessage());
} finally {
System.out.println("try-catch 블록 실행 완료 (항상 실행됩니다).");
}
System.out.println("------------------------------------");
// 2. Checked Exception 처리 예시 (IOException)
FileReader fr = null;
try {
fr = new FileReader("nonExistentFile.txt"); // FileNotFoundException (IOException의 하위 클래스)
int data;
while ((data = fr.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
System.err.println("파일을 읽는 도중 오류 발생: " + e.getMessage());
} finally {
if (fr != null) {
try {
fr.close(); // 자원 해제 (닫을 때도 예외 발생 가능성이 있어 try-catch 필요)
System.out.println("파일 리더가 닫혔습니다.");
} catch (IOException e) {
System.err.println("파일 리더를 닫는 도중 오류 발생: " + e.getMessage());
}
}
}
System.out.println("------------------------------------");
// 3. throws를 사용한 예외 전파 예시
try {
divide(10, 0);
} catch (ArithmeticException e) {
System.err.println("divide 메서드에서 예외 발생: " + e.getMessage());
}
System.out.println("프로그램 종료.");
}
// throws 키워드를 사용하여 ArithmeticException을 호출한 쪽으로 던짐
public static void divide(int a, int b) throws ArithmeticException {
if (b == 0) {
throw new ArithmeticException("0으로 나눌 수 없습니다."); // 직접 예외 발생
}
System.out.println("나눗셈 결과: " + (a / b));
}
}
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class TryWithResourcesExample {
public static void main(String[] args) {
// try-with-resources를 사용하여 FileReader를 자동으로 닫음
try (BufferedReader br = new BufferedReader(new FileReader("example.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.err.println("파일 처리 중 오류 발생: " + e.getMessage());
}
}
}
// Checked Exception으로 사용자 정의 예외 생성
class InsufficientFundsException extends Exception {
public InsufficientFundsException(String message) {
super(message);
}
}
public class CustomExceptionExample {
private static double balance = 1000;
public static void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException("잔액이 부족합니다. 현재 잔액: " + balance + "원");
}
balance -= amount;
System.out.println(amount + "원 인출 완료. 남은 잔액: " + balance + "원");
}
public static void main(String[] args) {
try {
withdraw(500);
withdraw(700); // InsufficientFundsException 발생
} catch (InsufficientFundsException e) {
System.err.println("인출 실패: " + e.getMessage());
}
}
}
import java.io.File;
import java.io.IOException;
public class ThrowExample {
// 이 메서드는 파일을 읽는 도중 IOException이 발생할 수 있음을 throws로 명시
public static void readFileContent(String filePath) throws IOException {
File file = new File(filePath);
if (!file.exists()) {
// 파일이 존재하지 않으면 IOException을 발생시켜 호출자에게 던짐
throw new IOException("파일을 찾을 수 없습니다: " + filePath);
}
// 실제 파일 읽기 로직 (생략)
System.out.println(filePath + " 파일을 성공적으로 읽었습니다.");
}
public static void main(String[] args) {
try {
// readFileContent 메서드 호출 시 예외 발생 가능성이 있으므로 try-catch로 잡음
readFileContent("myDocument.txt");
readFileContent("nonExistentFile.txt"); // 여기서 IOException이 발생하여 catch 블록으로 이동
} catch (IOException e) {
// 던져진 예외를 main 메서드에서 처리
System.err.println("메인에서 파일 관련 오류 처리: " + e.getMessage());
}
System.out.println("프로그램 계속 실행...");
}
}
이 둘의 차이는 간단히 말하자면 try는 개발자가 직접 처리하는 것이고 throw는 책임음 떠넘기는 것이다. try는 완전히 복구할 수 있고 프로그램이 안정적으로 지속되어야 할 때 사용한다. 즉, 책임지고 처리하겠다는 거다. 반면 throw는 명확히 처리할 수 없고 책임을 호출자에게 넘길 때 사용한다. 즉, 이건 너의 문제다라고 선언하는 것과 같다.
이 둘을 명확하게 구분해서 사용하기보다는 저수준에서 throw로 던져서 상위 계층이나 ui 계층에서 try-catch로 처리하는 패턴이 주로 나타난다.