JAVA
[Java] 예외 처리
kimyongjun0129
2025. 4. 23. 23:18
목차
예외처리가 필요한 이유
- 프로그램이 비정상 종료되는 것을 방지한다.
- 사용자에게 친절한 오류 메시지를 제공한다.
- 자원 낭비를 방지한다. (열린 파일, 네트워크 연결 등)
- 오류 추적 용이하다. (로그 기록)
예외처리
프로그램 실행 중에 발생하는 오류 상황을 예외(Exception) 처리하기 위한 방법이다.
키워드
키워드 | 설명 |
try | 예외가 발생할 수 있는 코드를 작성하는 블록이다. |
catch | 예외를 잡아서 처리하는 블록이다. |
finally | 예외 발생 여부와 관계없이 실행하는 블록이다. |
throw | 새로운 예외를 발생시킨다. |
throws | 발생시킨 예외를 메서드 밖으로 던진다. |
예외 계층 그림
- Throwable : 최상위 예외이다. 하위에 Exception과 Error가 있다.
- Error : Java에서 심각한 시스템 레벨의 문제를 나타내는 클래스이다. 개발자가 직접 처리할 필요가 없다.
- 각 예외 모두 객체이다. 따라서 최상위 객체가 Object인 것을 알 수 있다.
- 상위 오류를 잡으면, 하위 오류까지 한 번에 잡을 수 있다.
- Exception을 잡으면, SQLException과 IOException을 잡을 수 있다.
- RunTimeException을 잡으면, NullPointerException과 IllegalArgumentException을 잡을 수 있다.
예외 기본 규칙
예외는 2가지 규칙이 있다. 예외가 발생하면 잡아서 처리하거나, 처리할 수 없으면 밖으로 던져야 한다.
예외 처리
- 예를 들어, Exception을 catch로 잡으면 그 하위 예외들도 모두 잡을 수 있다. (상속의 다형성)
예외 던짐
- 예를 들어, Exception을 throws로 던지면 그 하위 예외들도 모두 던질 수 있다. (상속의 다형성)
예외 처리 실패
- 계속 밖으로 던지다가 마침내, 자바 main() 밖으로 예외를 던지면 예외 로그를 출력하면서 시스템이 종료된다.
체크 예외 vs 언체크 예외(런타임 예외)
체크 예외(Check) : 발생한 예외를 개발자가 명시적으로 처리해야 한다. 그렇지 않으면 컴파일 오류가 발생한다.
언체크 예외(UnCheck) : 개발자가 발생한 예외를 명시적으로 처리하지 않아도 된다.
체크 예외 처리
(1) 체크 예외처리 예제
메인 코드
더보기
public class MyCheckedException extends Exception {
public MyCheckedException(String message) {
super(message);
}
}
public class Client {
public void call() throws MyCheckedException{
throw new MyCheckedException("ex");
}
}
- 오류 생성 시점
- throws MyCheckedException을 통해 메서드 밖으로 던졌다. call()을 호출한(Service 객체) 곳으로 던졌다.
public class CheckedCatchMain {
public static void main(String[] args) {
Service service = new Service();
service.callCatch();
System.out.println("정상 종료");
}
}
예외 처리 안 한 경우
public class Service {
Client client = new Client();
public void callCatch() {
client.call();
}
}
- 오류 지점 : client.call();
- 오류 종류 : Unhandled exception: exception.basic.MyCheckedException (해당 오류에 대한 예외처리를 안해줬다는 의미)
- 오류 해결 방법 :
- throws MyCheckedException을 통해 메서드 밖으로 오류를 던진다.
- try~catch 문을 통해 MyCheckedException 오류를 예외처리 한다.
예외 처리 한 경우
public class Service {
Client client = new Client();
public void callCatch() {
try {
client.call();
} catch (MyCheckedException e) {
System.out.println("예외 처리, message : " + e.getMessage());
}
System.out.println("정상 흐름");
}
}
- 오류 지점 : client.call();
- 오류 종류 : Unhandled exception: exception.basic.MyCheckedException (해당 오류에 대한 예외처리를 안해줬다는 의미)
- 오류 해결 방법 :
- throws MyCheckedException을 통해 메서드 밖으로 오류를 던진다.
- try~catch 문을 통해 MyCheckedException 오류를 예외처리 한다.
예외처리 실패한 경우
어느 곳에서도 예외처리를 하지 않고 결국 main() 밖으로 오류를 던지면, 다음과 같은 오류가 발생한다.
오류를 예외처리하지 않고 모든 곳에서 던져서 결국 main() 밖으로도 던져지게 되면, 다음과 같이 stack trace를 통해 단계별로 어디서 예외를 던졌는지 보여주면서 런타임 오류가 발생한다.
언체크 예외 처리
(1) 언체크 예외 처리
- 컴파일러가 예외를 체크하지 않는다.
- 예외를 던지는 thorws를 선언하지 않고, 생략할 수 있다. 생략한 경우 자동으로 예외를 던진다.
(2) 언체크 예외 처리 예제
메인 코드
더보기
public class MyUncheckedException extends RuntimeException {
public MyUncheckedException(String message) {
super(message);
}
}
public class Client {
public void call() {
throw new MyUncheckedException("ex");
}
}
- 오류 생성 시점
- thorws 키워드를 사용하지 않아도 컴파일 오류가 생성되지 않는다.
public class UncheckedCatchMain {
public static void main(String[] args) {
Service service = new Service();
service.callCatch();
System.out.println("정상 로직");
}
}
예외 처리 한 경우
public class Service {
Client client = new Client();
public void callCatch() {
try {
client.call();
} catch (RuntimeException e) {
System.out.println("예외 처리, message: " + e.getMessage());
}
}
}
- 만약, 예외처리를 안한다면 런타임 오류가 발생한다.
예외처리 실패한 경우
public class Service {
Client client = new Client();
public void callCatch() {
client.call();
}
}
자동으로 계속 밖으로 던지다가, 결국 main 밖으로 던지게 되면서 런타임 오류가 발생한다.
언체크 예외이므로 예외처리를 안해도 자동으로 예외를 던져준다.
finally 블록
(1) finally
예외 발생 여부와 관계없이 항상 실행된다.
- 주로 자원 해제 (파일 닫기, DB 연결 종료 등)에 사용된다.
(2) finally 사용 방법
try {
// 여러 예외 발생 기능
} catch ( ....Exception e) {
// 예외 처리
} finally {
// 연결 해제 등
}
- 예외가 발생하지 않아도 finally는 실행된다.
- 예외 처리 해주어도 finally는 실행된다.
- 예외가 발생해서 오류가 터져도 finally는 실행된다.
(3) finally 예제
메인 코드
더보기
package exception.ex2;
public class NetworkClientExceptionV2 extends Exception{
private String errorCode;
public NetworkClientExceptionV2(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public String getErrorCode() {
return errorCode;
}
}
package exception.ex2;
public class NetworkClientV2 {
private final String address;
public boolean connectError;
public boolean sendError;
public NetworkClientV2(String address) {
this.address = address;
}
public void connect() throws NetworkClientExceptionV2{
if (connectError) {
// new NetworkClientExceptionV2("connectError", address + " 서버 연결 실패");
throw new RuntimeException();
}
// 연결 성공
System.out.println(address + " 서버 연결 성공");
}
public void send(String data) throws NetworkClientExceptionV2{
if (sendError) {
throw new NetworkClientExceptionV2("sendError", address + " 서버 데이터 전송 실패");
}
// 전송 성공
System.out.println(address + " 서버에 데이터 전송 : " + data);
}
public void disconnect() {
System.out.println(address + " 서버 연결 해제");
}
public void initError(String data) {
if (data.contains("error1")) {
connectError = true;
}
if (data.contains("error2")) {
sendError = true;
}
}
}
package exception.ex2;
import java.util.Scanner;
public class MainV2 {
public static void main(String[] args) throws NetworkClientExceptionV2{
NetworkServiceV2_5 networkService = new NetworkServiceV2_5();
Scanner scanner = new Scanner(System.in);
while(true) {
System.out.print("전송할 문자: ");
String input = scanner.nextLine();
if (input.equals("exit")) break;
networkService.sendMessage(input);
System.out.println();
}
System.out.println("프로그램을 정상 종료합니다.");
}
}
package exception.ex2;
public class NetworkServiceV2_5 {
public void sendMessage(String data) {
String address = "http://example.com";
NetworkClientV2 client = new NetworkClientV2(address);
client.initError(data);
try {
client.connect();
client.send(data);
} catch (NetworkClientExceptionV2 e) {
System.out.println("[오류] 코드: " + e.getErrorCode() + ", 메시지: " + e.getMessage());
} finally {
client.disconnect();
}
}
}