본 글은 김영한 님의 『김영한의 실전 자바 - 중급 1편』 강의를 학습하며 정리한 내용입니다.
강의 자료에 포함된 일부 코드와 이미지를 참고하여 발췌·활용하였습니다.자바 기본기를 제대로 다지고 싶으시다면, 아래 링크에서 강의를 확인해 보세요
『김영한의 실전 자바 - 중급 1편』 보러 가기
본게시물은 파트너스 활동의 일환으로 작성되었으며, 구매 시 소정의 수수료를 받을 수 있습니다.
[김영한의 실전 자바 - 중급 1편| 김영한 - 인프런 강의
현재 평점 5.0점 수강생 10,983명인 강의를 만나보세요. 실무에 필요한 자바의 다양한 중급 기능을 예제 코드로 깊이있게 학습합니다. 실무에 필요한 다양한 자바 중급 기능, Object, 불변 객체, String
www.inflearn.com](https://inf.run/Vvs5C)
예외 계층
자바는 프로그램 실행 중에 발생할 수 있는 예상치 못한 상황, 즉 예외(Exception) 을 처리하기 위한 메커니즘을 제공한다.
또한 다음 키워드들을 제공한다.try, catch, finally, throw, throws
예외 계층 그림

Object- 자바에서 기본형을 제외한 모든 것은 객체이다.
- 예외도 객체이므로 최상위 부모는
Object이다.
Throwable- 최상위 예외이다.
Error- 메모리 부족이나 심각한 시스템 오류와 같이 애플리케이션에서 복구가 불가능한 시스템 예외이다.
- 애플리케이션 개발자는 이 예외를 잡으려고 해서는 안된다.
Exception- 애플리케이션 로직에서 사용할 수 있는 실질적인 최상위 예외이다.
Exception과 그 하위 예외는 모두 컴파일러가 체크하는 체크 예외이다.RuntimeException은 언체크 예외이다.
RuntimeException- 컴파일러가 체크하지 않는 언체크 예외이다.
체크 예외 vs 언체크 예외
- 체크 예외
- 발생한 예외를 개발자가 명시적으로 처리해야 한다.
- 처리하지 않으면 컴파일 오류가 발생한다.
- 언체크 예외
- 개발자가 발생한 예외를 명시적으로 처리하지 않아도 된다.
예외 기본 규칙
예외는 폭탄 돌리기와 같다.
예외가 발생하면 잡아서 처리하거나, 처리할 수 없으면 밖으로 던져야한다.
기본 규칙
- 예외는 잡아서 처리하거나 밖으로 던져야 한다.
- 예외를 잡거나 던질 때 지정한 예외뿐만 아니라 그 예외의 자식들도 함께 처리할 수 있다.
Exception을catch로 잡으면 그 하위 예외들도 모두 잡을 수 있다.Excpetion을throws로 던지면 그 하위 예외들도 모두 던질 수 있다.
- 예외를 처리하지 못하고 계속 던지면 예외 로그를 출력하면서 시스템이 종료된다.
try-catch
- 예외를 잡아서 처리하려면
try ~ catch()사용해 예외를 잡으면 된다. try코드 블럭에서 발생하는 예외를 잡아서catch로 넘긴다.try코드 에서 잡은 예외가 없다면 예외를 밖으로 던져야 한다.
try-catch-finally
try {
//정상 흐름
} catch {
//예외 흐름
} finally {
//반드시 호출해야 하는 마무리 흐름
}
try를 시작하기만 하면,finally코드 블럭은 어떤 경우라도 반드시 호출된다.
throw, throws
throw 예외- 새로운 예외를 발생시킬 수 있다.
- 예외도 객체이기 때문에
new로 생성하고 예외를 발생시켜야 한다.
throws 예외- 발생시킨 예외를 메서드 밖으로 던질 때 사용한다.
throw예외는throws에 선언된 타입이거나 그 하위 타입이어야 한다.
체크 예외
RuntimeException 을 제외한 Excpetion 과 그 하위 예외는 모두 컴파일러가 체크하는 예외이다.
체크 예외는 잡아서 처리하거나, 밖으로 던지도록 선언해야 한다.
코드를 통해서 알아보자.
public class MyCheckedException extends Exception {
public MyCheckedException(String message) {
super(message);
}
}
Exception을 상속받은 예외는 체크 예외가 된다.- 예외가 제공하는 기본 기능이 있는데, 그 중에 오류 메시지를 보관하는 기능도 있다. 생성자를 통해서 해당 기능을 그대로 사용하면 편리하다.
super(message)로 전달한 메시지는Throwable에 있는detailMessage에 보관된다.getMessage()를 통해 조회할 수 있다.
public class Client {
public void call() throws MyCheckedException{
throw new MyCheckedException("ex");
}
}
throw 예외- 새로운 예외를 발생시킬 수 있다.
- 예외도 객체이기 때문에
new로 생성하고 예외를 발생시켜야 한다.
throws 예외- 발생시킨 예외를 메서드 밖으로 던질 때 사용한다.
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("정상 흐름");
}
/**
* 체크 예외를 밖으로 던지는 코드
* 체크 예외는 예외를 잡지 않고 밖으로 던지려면 throws 예외를 메서드에 필수로 선언해야한다.
*/
public void callThrow() throws MyCheckedException{
client.call();
}
}
public class CheckedCatchMain {
public static void main(String[] args) {
Service service = new Service();
service.callCatch();
System.out.println("정상 종료");
}
}
--실행결과--
예외 처리, message=ex
정상 흐름
정상 종료
실행 순서를 분석해보자.
- main() → service.callCatch() → client.call() [예외 발생, 던짐]
- Client.call() 에서 MyCheckedException 예외가 발생하고, 그 예외를 Service.callCatch() 에서 잡는다.
- catch 로 예외를 잡아서 처리하고 나면 코드가 cath 블럭의 다음 라인으로 넘어가서 정상 흐름으로 작동한다.
public class CheckedThrowMain {
public static void main(String[] args) throws MyCheckedException {
Service service = new Service();
service.callThrow();
System.out.println("정상 종료");
}
}
--실행결과--
Exception in thread "main" exception.basic.checked.MyCheckedException: ex
at exception.basic.checked.Client.call(Client.java:5)
at exception.basic.checked.Service.callThrow(Service.java:28)
at exception.basic.checked.CheckedThrowMain.main(CheckedThrowMain.java:7)
실행 순서를 분석해보자.
- main() → service.callThrow() → client.call() [예외 발생, 던짐]
- Service.callThrow() 안에서 예외를 처리하지 않고, 던져서 main() 메서드까지 올라온다.
- main() 에서 예외를 처리하지 못했기 때문에 밖으로 던진다.
- 예외가 main() 밖으로 던져지면 예외 정보과 스택 트레이스를 출력하고 프로그램이 종료된다.
- 스택 트레이스 정보를 활용하면 예외가 어디서 발생했는지, 어떤 경로를 거쳐서 넘어왔는지 확인할 수 있다.
장단점
- 장점
- 개발자가 실수로 예외를 누락하지 않도록 컴파일러가 문제를 잡아준다.
- 단점
- 모든 체크 예외를 잡거나 던지도록 처리해야 하기 때문에, 번거롭다.
언체크 예외
RuntimeException 과 그 하위 예외는 언체크 예외이다.
말 그대로 컴파일러가 예외를 체크하지 않는다는 뜻이다.
체크 예외와의 차이는 예외를 던지는 throws 를 선언하지 않고 생략할 수 있다.
코드를 통해 살펴보자.
public class MyUncheckedException extends RuntimeException {
public MyUncheckedException(String message) {
super(message);
}
}
public class Client {
public void call(){
throw new MyUncheckedException("ex");
}
}
public class Service {
Client client = new Client();
public void callCatch() {
try {
client.call();
} catch (MyUncheckedException e) {
//예외 처리 로직
System.out.println("예외 처리, message=" + e.getMessage());
}
System.out.println("정상 로직");
}
public void callThrow() {
client.call();
}
}
public class UncheckedThrowMain {
public static void main(String[] args) {
Service service = new Service();
service.callThrow();
System.out.println("정상 종료");
}
}
실행 과정은 위에서 체크예외와 같다.
다른 점은 체크 예외와 다르게 throws 예외 를 선언하지 않은 점이다.
장단점
- 장점
- 신경쓰고 싶지 않은 언체크 예외를 무시할 수 잇다.
- 단점
- 실수로 예외를 누락할 수 있다.
예외 계층
예외를 단순히 오류 코드로 분류하는 것이 아니라, 예외를 계층화해서 다양하게 만들면 더 세밀하게 예외를 처리할 수 있다.

- NetworkClientExceptionV3
- NetworkClient 에서 발생하는 모든 예외는 이 예외의 자식이다.
- ConnectExceptionV3
- 연결 실패시 발생하는 예외
- SendExceptionV3
- 전송 실패시 발생하는 예외
이렇게 예외를 계층화하면 다음과 같은 장점이 있다.
- 부모 예외를 잡거나 던지면, 자식 예외도 함께 잡거나 던질 수 있다.
- 특정 예외를 잡아서 처리할 수 있다.
활용
모든 예외를 잡아서 처리하려면 마지막에 Exception 을 두면 된다.
예외가 발생했을 때 catch 를 순서대로 실행하므로, 더 디테일한 자식을 먼저 잡아야 한다.
try {
// 1. RuntimeException 발생
} catch (ConnectExceptionV3 e) { // 2. 대상이 다름
} catch (NetworkClientExceptionV3 e) { // 3.대상이 다름
} catch (Exception e) { // 4.Exception은 RuntimeException의 부모이므로 여기서 잡음
}
또한 다음과 같이 | 를 사용해서 여러 예외를 한번에 잡을 수 있다.
try {
client.connect();
client.send(data);
} catch (ConnectExceptionV3 | SendExceptionV3 e) {
System.out.println("[연결 또는 전송 오류] 주소: , 메시지: " + e.getMessage());
} finally {
client.disconnect();
}
이 경우 각 예외들의 공통 부모의 기능만 사용할 수 있다.
실무 예외 처리 방안
체크 예외의 부담
체크 예외는 개발자가 실수로 놓칠 수 있는 예외들을 컴파일러가 체크해준다.
처리할 수 없는 예외가 많아지고, 프로그램이 점점 복잡해지면서 체크 예외를 사용하는 것이 점점 더 부담스러워졌다.
체크 예외 사용 시나리오
- 실무에서는 수 많은 라이브러리를 사용하고, 또 다양한 외부 시스템과 연동한다.
- 사용하는 각각의 클래스들이 자신만의 예외를 모두 체크 예외로 만들어서 전달한다고 가정하자.
- 호출하는 어떤 클래스에서 던지는 체크 예외들을 처리해야 한다. 만약 처리할 수 없다면 밖으로 던져야 한다.
- 이 클래스에서 예외를 처리할 수 없는 예외들은 하나씩 밖으로 던져야 한다.
- 라이브러리가 늘어날수록 다루어야 하는 예외도 더 많아진다.
- 다룰 수 없는 수 많은 체크 예외 지옥에 빠지게 돼
Exception예외를 던진다.
throws Exception 의 문제
체크 예외의 최상위 타입인 Exception 을 던지게 되면 다른 체크 예외를 체크할 수 있는 기능이 무효화되고, 중요한 체크 예외를 다 놓치게 된다.
중간에 중요한 체크 예외가 발생해도 컴파일러는 Exception 을 던지기 때문에 문법에 맞다고 판단해서 컴파일 오류가 발생하지 않는다.
언체크(런타임) 예외 사용 시나리오
- 어떤 클래스에서 호출하는 클래스들이 언체크 예외를 전달한다고 가정해보자.
- 언체크 예외이므로
throws를 선언하지 않아도 된다. - 사용하는 라이브러리가 늘어나서 언체크 예외가 늘어도 본인이 필요한 예외만 잡으면 된다.
예외 공통처리
처리할 수 없는 예외들은 중간에 여러곳에서 나누어 처리하기 보다는 예외를 공통으로 처리할 수 있는 곳을 만들어서 한 곳에서 해결하면 된다.
public class Main {
public static void main(String[] args) {
NetworkServiceV4 networkService = new NetworkServiceV4();
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.print("전송할 문자: ");
String input = scanner.nextLine();
if (input.equals("exit")) {
break;
}
try {
networkService.sendMessage(input);
} catch (Exception e) { // 모든 예외를 잡아서 처리
exceptionHandler(e);
}
System.out.println();
}
System.out.println("프로그램을 정상 종료합니다.");
}
//공통 예외 처리
private static void exceptionHandler(Exception e) {
//공통 처리
System.out.println("사용자 메시지: 죄송합니다. 알 수 없는 문제가 발생했습니다.");
System.out.println("==개발자용 디버깅 메시지==");
e.printStackTrace(System.out); // 스택 트레이스 출력
//e.printStackTrace(); // System.err에 스택 트레이스 출력
//필요하면 예외 별로 별도의 추가 처리 가능
if (e instanceof SendExceptionV4 sendEx) {
System.out.println("[전송 오류] 전송 데이터: " + sendEx.getSendData());
}
}
}
- exceptionHandler()
- 해결할 수 없는 예외가 발생하면 사용자에게는 시스템 내에 알 수 없는 문제가 발생했다고 알리는 것이 좋다.
- 개발자는 빨리 문제를 찾고 디버깅 할 수 있도록 오류 메시지를 남겨두어야 한다.
- 예외도 객체이므로 필요하면
instanceof와 같이 예외 객체의 타입을 확인해서 별도의 추가 처리를 할 수 있다.
- e.printStackTrace()
- 예외 메시지와 스택 트레이스를 출력할 수 있다.
- 예외가 발생한 지점을 역으로 추적할 수 있다.
System.err표준 오류에 결과를 출력한다.- 출력 결과를 빨간색으로 보여준다.
try-with-resources
애플리케이션에서 외부 자원을 사용하는 경우 반드시 외부 자원을 해제해야 한다.
자바에서는 try-with-resources 라는 편의 기능을 자바 7에서 도입했다.
이 기능을 사용하려면 먼저 AutoCloseable 인터페이스를 구현해야 한다.
이 인터페이스를 구현하면 try 가 끝나는 시점에 close() 가 자동으로 호출된다.
try (Resource resource = new Resource()) {
// 리소스를 사용하는 코드
}
try블럭이 끝나면 자동으로AutoCloseable.close()를 호출해서 자원을 해제한다.catch블럭 없이try블럭만 있어도close()는 호출된다.
public class NetworkClientV5 implements AutoCloseable {
...
@Override
public void close() {
System.out.println("NetworkClientV5.close");
disconnect();
}
}
- close()
AutoCloseable인터페이스가 제공하는 메서드는try가 끝나면 자동으로 호출된다.- 종료 시점에 자원을 반납하는 방법을 여기에 정의하면 된다.
장점
- 리소스 누수 방지
- 모든 리소스가 제대로 닫히도록 보장한다.
- 코드 간결성 및 가독성 향상
- 명시적인 close() 호출이 필요 없다.
- 스코프 범위 한정
- 리소스로 사용되는 변수의 스코프가
try블럭 안으로 한정된다.
- 리소스로 사용되는 변수의 스코프가
- 조금 더 빠른 자원 해제
- 기존에는
try catch finally로catch이후에 자원을 반납했다.Try with resources
구분은try블럭이 끝나면 즉시close()를 호출한다.
- 기존에는
'인프런 > 김영한 자바' 카테고리의 다른 글
| [김영한의 실전 자바 중급 1편] - 7. 중첩 클래스, 내부 클래스 (0) | 2025.12.14 |
|---|---|
| [김영한의 실전 자바 중급 1편] - 6. 날짜와 시간 (0) | 2025.10.16 |
| [김영한의 실전 자바 중급 1편] - 5. 열거형,ENUM (0) | 2025.10.14 |
| [김영한의 실전 자바 중급 1편] - 4. 래퍼,Class 클래스 (0) | 2025.10.12 |
| [김영한의 실전 자바 중급 1편] - 3. String 클래스 (0) | 2025.10.10 |