[JAVA] 예외 처리
[JAVA] 예외 처리
예외처리가 필요한 이유
- 외부 시스템(서버, DB, 파일시스템) 은 항상 실패 가능성이 존재
- ex) 서버 연결 실패, 네트워크 끊김 등
- 정상 흐름과 예외 흐름이 섞이면 가독성이 떨어짐
- 외부 자원을 사용한 후 연결을 해제해 외부 자원을 반드시 반납해야 한다.
예외 처리는 오류 발생 시 프로그램이 비정상 종료되지 않고, 정상 흐름과 분리해서 코드를 깔끔하게 유지하기 위한 핵심 메커니즘
예외 처리의 두가지 규칙
- 예외는 잡아서 처리하거나 밖으로 던저야한다.
- 예외를 잡거나 던질 때 지정한 예외뿐만 아니라 그 예외의 자식들도 함께 처리 할 수 있다.
예외를 계속 던지면
main()밖으로 예외 로그를 출력하며 시스템이 종료된다.
예외계층 구조
Exception: 체크 예외
Exception과 하위 예외는 컴파일러가 체크하는 예외 단,RuntimeException은 제외
RuntimeException: 언체크 예외
- 컴파일러가 체크하지않는 언체크 예외
- 자식도 모두 언체크 예외 자식 포함 모두 런타임 예외라 많이 부름
체크 예외와 언체크 예외
체크 예외는 발생한 예외를 개발자가 명시적으로 처리해야한다. 그렇지 않으면 컴파일 오류가 발생한다. 반면 언체크 예외는 개발자가 예외를 명시적으로 처리하지 않아도 된다.
체크예외
Exception계열 (RuntimeException제외)- 반드시
catch하거나throws선언 필요 - ex)
IOException,SQLException
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
32
33
34
35
36
37
38
39
40
41
42
43
/**
* Exception 상속 -> 체크 예외
*/
public class MyCHeckedException extends Exception {
public MyCHeckedException(String message) {
super(message);
}
}
public class Service {
Client client = new Client();
/**
* 나 처리 할 수 있어
* 예외를 잡아서 처리
*/
public void callCatch() {
try{
client.call();
} catch (MyCHeckedException e) { //MyCHeckedException 은 Exception 의 자식이니 Exception 사용 가능
//예외 처리 로직
System.out.println("예외처리, message = " + e.getMessage());
}
System.out.println("정상 흐름~~~~");
}
/**
* 나 처리 못하겠는데?
* 체크 예외를 밖으로 던지는 코드
* 체크 예외는 예외를 잡지 않고 밖으로 던지려면 throws 예외를 메서드에 필수로 선언해야한다.
*/
public void callThrow() throws MyCHeckedException {
client.call();
}
}
public class Client {
public void call() throws MyCHeckedException {
//문제 발생
throw new MyCHeckedException("ex");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class CheckedCatchMain {
public static void main(String[] args) {
Service service = new Service();
service.callCatch();
System.out.println("정상 종료");
}
}
//실행결과
예외처리, message = ex
정상 흐름~~~~
정상 종료
1
2
3
4
5
6
7
8
9
10
11
12
13
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:6)
at exception.basic.checked.Service.callThrow(Service.java:27)
at exception.basic.checked.CheckedThrowMain.main(CheckedThrowMain.java:6)
언체크 예외
RuntimeException계열- 컴퍼일러 강제 없음 → 필요할 때만 잡음
- ex)
NullPointerException,IllegalArgumentException
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
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* RuntimeException을 상속받는 예외는 언체크 예외가 된다.
*/
public class MyUncheckedException extends RuntimeException {
public MyUncheckedException(String message) {
super(message);
}
}
/**
* unChecked 예외는
* 예외를 잡거나 던지지 않아도 된다.
* 예외를 잡지 않으면 자동으로 밖으로 빠져나간다.
*/
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("정상 로직~~");
}
/**
* 예외를 잡지 않아도 된다. 자연스럽게 상위로 넘어간다.
* 체크 예외와 다르게 throws 예외 선언을 하지 않아도 된다.
*/
public void callThrow () {
client.call();
}
}
public class Client {
public void call() {
throw new MyUncheckedException("ex");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class UncheckedCatchMain {
public static void main(String[] args) {
Service service = new Service();
service.callCatch();
System.out.println("정상 종료");
}
}
//실행결과
예외 처리, message = ex
정상 로직~~
정상 종료
1
2
3
4
5
6
7
8
9
10
11
12
13
public class UncheckedThrowMain {
public static void main(String[] args) {
Service service = new Service();
service.callThrow(); //안에서 예외를 처리하지 않고, 밖으로 던졌기 때문에 main() 까지 올라감
System.out.println("정상 종료");
}
}
//실행결과
Exception in thread "main" exception.basic.unchecked.MyUncheckedException: ex
at exception.basic.unchecked.Client.call(Client.java:5)
at exception.basic.unchecked.Service.callThrow(Service.java:31)
at exception.basic.unchecked.UncheckedThrowMain.main(UncheckedThrowMain.java:6)
체크 예외와 언체크 예외의 차이는 예외를 처리할 수 없을 때 예외를 밖으로 던지는 부분에 있다. 이 부분을 필수로 선언 해야 하는지 생략 할 것인지의 차이
Spring 이나 JPA 같은 기술들도 대부분 언체크 예외를 사용한다. 런타임 예외도 필요하면 잡을 수 있기 때문에 필요한 경우 잡아서 처리하고, 그렇지 않으면 자연스럽게 던지도록 한다. 처리할 수 없는 예외는 예외 공통 처리 부분을 만들어 해결한다. 최근 추세는 언체크 예외를 주로 쓴다.
finally
자바는 어떤 경우에도 반드시 호출되는 finally 기능을 제공한다.
1
2
3
4
5
6
7
try {
//정상흐름
} catch {
//예외흐름
} finally {
//반드시 호출해야 하는 마무리 흐름
}
try를 시작하면finally코드 블럭은 어떤 경우에도 반드시 호출try,catch안에서 잡을 수 없는 예외가 발생해도finally는 반드시 호출catch없이try~finally만 사용할 수도 있다.
try~with~resources
try가 끝나면 외부 자원을 반납해주는 편의기능을 자바 7에서부터 추가
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class NetworkClientV5 implements AutoCloseable { //AutoCloseable를 구현
private final String address;
public boolean connectError;
public boolean sendError;
public NetworkClientV5(String address) {
this.address = address;
}
public void connect() {
if(connectError) {
//오류 코드를 리턴하는게 아닌 예외를 던짐
throw new ConnectExceptionV4(address,address + "서버 연결 실패");
}
//연결 성공
System.out.println(address + " 서버 연결 성공!!");
}
public void send(String data) {
if(sendError) {
//오류 코드를 리턴하는게 아닌 예외를 던짐
throw new SendExceptionV4(data,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;
}
}
@Override
public void close() { //try 가 끝나면 자동 호출 자원반납을 여기다 정의
System.out.println("NetworkClientV5.close");
disconnect();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class NetworkServiceV5 {
public void sendMessage(String data) {
String address = "http://example.com";
//try가 끝나면 close 자동 호출
try(NetworkClientV5 client = new NetworkClientV5(address)) {
client.initError(data);
client.connect(); //여기서 터져도 close가 먼저 실행되어 자원적으로 이득 가독성도 좋아짐
client.send(data);
} catch (Exception e) { //catch 블럭없이 try 만있어도 close()는 호출
System.out.println("예외 확인: " + e.getMessage());
throw e;
}
}
}
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class MainV4 {
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) { //Exception은 모든 예외를 다 잡음
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();
//공통 처리를 하지만 예외 별로 분류할 수 있음
if(e instanceof SendExceptionV4 sendEx) { //전송 오류는 다르게 봐야겠어!
System.out.println("[전송오류] 전송 데이터: " + sendEx.getSendData());
}
}
}
//실행결과
전송 문자: hello
http://example.com 서버 연결 성공!!
http://example.com 서버에 데이터 전송: hello
http://example.com 서버 연결 해제!!
전송 문자: error1
http://example.com 서버 연결 해제!!
ㅈㅅㅈㅅ 알 수 없는 오류! ㅋㅋㅋ
==개발자용 디버깅 메세지 출력==
exception.ex4.exception.ConnectExceptionV4: http://example.com서버 연결 실패
at exception.ex4.NetworkClientV4.connect(NetworkClientV4.java:19)
at exception.ex4.NetworkServiceV4.sendMessage(NetworkServiceV4.java:14)
at exception.ex4.MainV4.main(MainV4.java:21)
전송 문자: error2
http://example.com 서버 연결 성공!!
http://example.com 서버 연결 해제!!
ㅈㅅㅈㅅ 알 수 없는 오류! ㅋㅋㅋ
==개발자용 디버깅 메세지 출력==
exception.ex4.exception.SendExceptionV4: http://example.com서버 연결 실패
at exception.ex4.NetworkClientV4.send(NetworkClientV4.java:28)
at exception.ex4.NetworkServiceV4.sendMessage(NetworkServiceV4.java:15)
at exception.ex4.MainV4.main(MainV4.java:21)
[전송오류] 전송 데이터: error2
try-with-resources 장점
- 자원 자동 반환
AutoCloseable을 구현한 객체는try블록이 끝나면 자동으로close()호출finally없이도 자원정리
- 코드 간결성 및 가독성 향상
- 예외 누락 방지
try-with-resource는 자원을 안전하게 자동으로 해제하며, 코드가 간결해지고 예외처리도 명확해진다. 외부자원을 사용할 땐 무조건 사용하자.
This post is licensed under
CC BY 4.0
by the author.
