김영한의 실전 자바 - 중급 1편 강의 | 김영한 - 인프런
김영한 | , 국내 개발 분야 누적 수강생 1위, 제대로 만든 김영한의 실전 자바[사진][임베딩 영상]단순히 자바 문법을 안다? 이걸로는 안됩니다!전 우아한형제들 기술이사, 누적 수강생 40만 명 돌
www.inflearn.com
예외 계층
자바는 프로그램 실행 중에 발생할 수 있는 예상치 못한 상황, 즉 예외(Exception)를 처리하기 위한 메커니즘을 제공한다. 이는 프로그램의 안정성과 신뢰성을 높이는 데 중요한 역할을 한다.
자바의 예외 처리는 다음 키워드를 사용한다.
- `try`, `catch`, `finally`, `throw`, `throws`
- `Object`: 자바에서 기본형을 제외한 모든 것은 객체다. 예외도 객체이다. 모든 객체의 최상위 부모는 `Object`이므로 예외의 최상위 부모도 `Object`이다.
- `Throwable`: 최상위 예외이다. 하위에 `Exception`과 `Error`가 있다.
- `Error`: 메모리 부족이나 심각한 시스템 오류와 같이 애플리케이션에서 복구가 불가능한 시스템 예외, 애플리케이션 개발자는 이 예외를 잡으려고 해서는 안된다.
- `Exception`: 체크 예외
- 애플리케이션 로직에서 사용할 수 있는 실질적인 최상위 예외
- `Exception`과 그 하위 예외는 모두 컴파일러가 체크하는 체크 예외이다. 단 `RuntimeException`은 예외로 한다.
- `RuntimeException`: 언체크 예외, 런타임 예외
- 컴파일러가 체크하지 않는 언체크 예외이다.
- `RuntimeException`과 그 자식 예외는 모두 언체크 예외이다.
- `RuntimeException`의 이름을 따라서 `RuntimeException`과 그 하위 언체크 예외를 런타임 예외라고 많이 부른다.
예외 기본 규칙
예외는 폭탄 돌리기와 같다. 예외가 발생하면 잡아서 처리하거나, 처리할 수 없으면 밖으로 던져야 한다.
- `Main`은 `Service`를 호출한다.
- `Service`는 `Client`를 호출한다.
- `Client`에서 예외가 발생했다.
- `Client`에서 예외를 처리하지 못하고 밖으로 던진다. 여기서 `Client`의 밖은 `Client`를 호출한 `Service`를 뜻한다.
- `Service`에 예외가 전달된다. `Service`에서 예외를 처리했다. 이후에는 애플리케이션 로직이 정상 흐름으로 동작한다.
- 정상 흐름을 반환한다.
예외의 2가지 기본 규칙
- 예외는 잡아서 처리하거나 밖으로 던져야 한다.
- 예외를 잡거나 던질 때 지정한 예외뿐만 아니라 그 예외의 자식들도 함께 처리할 수 있다.
- 예를 들어서 `Exception`을 `catch`로 잡으면 그 하위 예외들도 모두 잡을 수 있다.
- 예를 들어서 `Exception`을 `throws`로 던지면 그 하위 예외들도 모두 던질 수 있다.
자바를 `main()` 밖으로 예외를 던지면 예외 로그를 출력하면서 시스템이 종료된다.
체크 예외
- `Exception`과 그 하위 예외는 모두 컴파일러가 체크하는 체크 예외이다. 단 `RuntimeException`은 예외로 한다.
- 체크 예외는 잡아서 처리하거나, 또는 밖으로 던지도록 선언해야 한다. 그렇지 않으면 컴파일 오류가 발생한다.
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("정상 흐름");
}
public void catchThrow() throws MyCheckedException {
client.call();
}
}
체크 예외를 잡아서 처리
public class CheckedCatchMain {
public static void main(String[] args) {
Service service = new Service();
service.callCatch();
System.out.println("정상 종료");
}
}
실행 결과
예외 처리, message=ex
정상 흐름
정상 종료
- `service.catchCall()`에서 예외를 처리했기 때문에 `main()` 메서드까지 예외가 올라오지 않는다.
- `main()`에서 출력하는 "정상 종료" 문구가 출력된 것을 확인할 수 있다.
실행 순서
- `main()` → `service.callCatch()` → `client.call()` [예외 발생, 던짐]
- `main()` ← `service.callCatch()` [예외 처리] ← `client.call()`
- `main()` [정상 흐름] ← `service.callCatch()` ← `client.call()`
public void callCatch() {
try {
client.call();
} catch (MyCheckedException e) {
System.out.println("예외 처리, message=" + e.getMessage());
}
System.out.println("정상 흐름");
}
- 예외를 잡아서 처리하려면 `try ~ catch( . . )`를 사용해서 예외를 잡으면 된다.
- `try` 코드 블럭에서 발생하는 예외를 잡아서 `catch`로 넘긴다.
- 만약 `try`에서 잡은 예외가 `catch`의 대상에 없으면 예외를 잡을 수 없다. 이때는 예외를 밖으로 던져야 한다.
- 여기서는 `MyCheckedException` 예외를 `catch`로 잡아서 처리한다.
public void callCatch() {
try {
client.call();
} catch (Exception e) {
System.out.println("예외 처리, message=" + e.getMessage());
}
System.out.println("정상 흐름");
}
- `catch`에 `MyCheckedException`의 상위 타입인 `Exception`을 적어주어도 `MyCheckedException`을 잡을 수 있다.
- `catch`에 예외를 지정하면 해당 예외와 그 하위 타입 예외를 모두 잡아준다.
- 예외도 객체이기 때문에 다형성이 적용된다.
예외를 처리하지 않고 던지기
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)
- `Service.callThrow()`안에서 예외를 처리하지 않고, 밖으로 던졌기 때문에 예외가 `main()` 메서드까지 올라온다.
- `main`의 `service.callThrow()`를 호출하는 곳에서 예외를 잡아서 처리하지 못했기 때문에 여기서 예외가 `main()` 밖으로 던져진다. 따라서 `main()`에 있는 `service.callThrwo()` 메서드 다음에 있는 "정상 종료"가 출력되지 않는다.
- 예외가 `main()` 밖으로 던져지면 예외 정보와 스택 트레이스(Stack Trace)를 출력하고 프로그램이 종료된다.
실행 순서
- `main()` → `service.callThrow()` → `client.call()` [예외 발생, 던짐]
- `main()` ← `service.callThrow()` [예외 던짐] ← `client.call()`
- `main()` [예외 던짐] ← `service.callThrow()` ← `client.call()`
public void callThrow() throws MyCheckedException {
client.call();
}
- 체크 예외를 처리할 수 없을 때는 `throws` 키워드를 사용해서, `method() throws 예외`와 같이 밖으로 던질 예외를 필수로 지정해주어야 한다. 여기서는 `MyCheckedException`을 밖으로 던지도록 지정해주었다.
public void callThrow() {
client.call();
}
- `throws`를 지정하지 않으면 컴파일 오류가 발생한다.
- `client.call()`을 보면 `throws MyCheckedException`가 선언되어 있다. 따라서 `client.call()`을 호출하는 쪽에서 예외를 잡아서 처리하든, 던지든 선택해야 한다.
- 체크 예외는 잡아서 직접 처리하거나 또는 밖으로 던지거나 둘 중 하나로 개발자가 직접 명시적으로 처리해야 한다.
- 체크 예외는 `try ~ catch`로 잡아서 처리하거나 또는 `throws`를 지정해서 예외를 밖으로 던진다는 선언을 필수로 해주어야 한다.
public void callThrow() throws Exception {
client.call();
}
- `throws`에 `MyCheckedException`의 상위 타입인 `Exception`을 적어주어도 `MyCheckedException`을 던질 수 있다.
- `throws`에 지정한 타입과 그 하위 타입 예외를 모두 던진다.
- 예외도 객체이기 때문에 다형성이 적용된다.
체크 예외의 장단점
체크 예외는 예외를 잡아서 처리할 수 없을 때, 예외를 밖으로 던지는 `throws 예외`를 필수로 선언해야 한다. 그렇지 않으면 컴파일 오류가 발생한다.
- 장점: 개발자가 실수로 예외를 누락하지 않도록 컴파일러를 통해 문제를 잡아주는 훌륭한 안전 장치이다. 이를 통해 개발자는 어떤 체크 예외가 발생하는지 쉽게 파악할 수 있다.
- 단점: 개발자가 모든 체크 예외를 반드시 잡거나 던지도록 처리해야 하기 때문에, 너무 번거로운 일이 된다. 크게 신경쓰고 싶지 않은 예외까지 모두 챙겨야 한다.
언체크 예외
- `RuntimeException`과 그 하위 예외는 언체크 예외로 분류된다.
- 언체크 예외는 말 그대로 컴파일러가 예외를 체크하지 않는다는 뜻이다.
- 언체크 예외는 체크 예외와 기본적으로 동일하다. 차이가 있다면 예외를 던지는 `throws`를 선언하지 않고, 생략할 수 있다. 생략한 경우 자동으로 예외를 던진다.
체크 예외 vs 언체크 예외
- 체크 예외: 예외를 잡아서 처리하지 않으면 항상 `throws` 키워드를 사용해서 던지는 예외를 선언해야 한다.
- 언체크 예외: 예외를 잡아서 처리하지 않아도 `throws` 키워드를 생략할 수 있다.
public class MyUncheckedException extends RuntimeException {
public MyUncheckedException(String message) {
super(message);
}
}
public void callCatch() {
try {
client.call();
} catch (MyUncheckedException e) {
//예외 처리 로직
System.out.println("예외 처리, message=" + e.getMessage());
}
System.out.println("정상 로직");
}
- 언체크 예외도 필요한 경우 예외를 잡아서 처리할 수 있다.
public void callThrow() {
client.call();
}
- 언체크 예외는 체크 예외와 다르게 `throws 예외`를 선언하지 않아도 된다.
- 말 그대로 컴파일러가 이런 부분을 체크하지 않기 때문에 언체크 예외이다.
언체크 예외의 장단점
언체크 예외는 예외를 잡아서 처리할 수 없을 때, 예외를 밖으로 던지는 `throws` 예외를 생략할 수 있다.
- 장점: 신경쓰고 싶지 않은 언체크 예외를 무시할 수 있다. 체크 예외의 경우 처리할 수 없는 예외를 밖으로 던지려면 항상 `throws 예외`를 선언해야 하지만, 언케츠 예외는 이 부분을 생략할 수 있다.
- 단점: 언체크 예외는 개발자가 실수로 예외를 누락할 수 있다. 반면에 체크 예외는 컴파일러를 통해 예외 누락을 잡아준다.
예외 계층
설명
예외를 단순히 오류 코드로 분류하는 것이 아니라, 예외를 계층화해서 다양하게 만들면 더 세밀하게 예외를 처리할 수 있다.
- `NetworkClientExceptionV3`: `NetworkClient`에서 발생하는 모든 예외는 이 예외의 자식이다.
- `ConnectExceptionV3`: 연결 실패 시 발생하는 예외이다. 내부에 연결을 시도한 `address`를 보관한다.
- `SendExceptionV3`: 전송 실패 시 발생하는 예외이다. 내부에 전송을 시도한 데이터인 `sendData`를 보관한다.
이렇게 예외를 계층화하면 다음과 같은 장점이 있다.
- 부모 예외를 잡거나 던지면, 자식 예외도 함께 잡거나 던질 수 있다. 예를 들어서 `NetworkClientExceptionV3` 예외를 잡으면 그 하위인 `ConnectExceptionV3`, `SendExceptionV3` 예외도 함께 잡을 수 있다.
- 특정 예외를 잡아서 처리하고 싶으면 `ConnectExceptionV3`, `SendExceptionV3`와 같은 하위 예외를 잡아서 처리하면 된다.
예외
public class NetworkClientExceptionV3 extends Exception {
public NetworkClientExceptionV3(String message) {
super(message);
}
}
// 연결 실패 시 발생하는 예외
public class ConnectExceptionV3 extends NetworkClientExceptionV3 {
// 연결 시도한 address 보관
private final String address;
public ConnectExceptionV3(String address, String message) {
super(message);
this.address = address;
}
public String getAddress() {
return address;
}
}
// 전송 실패 시 발생하는 예외
public class SendExceptionV3 extends NetworkClientExceptionV3 {
// 전송을 시도한 데이터인 sendData 보관
private final String sendData;
public SendExceptionV3(String sendData, String message) {
super(message);
this.sendData = sendData;
}
public String getSendData() {
return sendData;
}
}
public class NetworkClientV3 {
private final String address;
public boolean connectError;
public boolean sendError;
public NetworkClientV3(String address) {
this.address = address;
}
public void connect() throws ConnectExceptionV3 {
if (connectError) {
throw new ConnectExceptionV3(address, address + " 서버 연결 실패");
}
System.out.println(address + " 서버 연결 성공");
}
public void send(String data) throws SendExceptionV3 {
if (sendError) {
throw new SendExceptionV3(data, address + " 서버에 데이터 전송 실패: " + data);
}
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;
}
}
}
- 오류 코드로 어떤 문제가 발생했는지 이해하는 것이 아니라 예외 그 자체로 어떤 오류가 발생했는지 알 수 있다.
- 연결 관련 오류가 발생하면 `ConnectExceptionV3`를 던지고, 전송 관련 오류가 발생하면 `SendExceptionV3`을 던진다.
public class MainV3 {
public static void main(String[] args) {
NetworkServiceV3_2 networkService = new NetworkServiceV3_2();
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("프로그램을 정상 종료합니다.");
}
}
실행 결과
전송할 문자: hello
https://example.com 서버 연결 성공
https://example.com 서버에 데이터 전송: hello
https://example.com 서버 연결 해제
전송할 문자: error1
[연결 오류] 주소: https://example.com, 메시지: https://example.com 서버 연결 실패
https://example.com 서버 연결 해제
전송할 문자: error2
https://example.com 서버 연결 성공
[전송 오류] 전송 데이터: error2, 메시지: https://example.com 서버에 데이터 전송 실패: error2
https://example.com 서버 연결 해제
전송할 문자: exit
프로그램을 정상 종료합니다.
실행 결과를 보면 `ConnectExceptionV3`, `SendExceptionV3`이 발생한 각각의 경우에 출력된 오류 메시지가 다른 것을 확인할 수 있다.
활용
`NetworkClientV3`에서 수많은 예외가 발생하는 경우, 모든 예외를 하나하나 다 잡아서 처리하는 것은 상당히 번거롭다. 다음과 같이 예외를 처리하도록 구성해보자.
- 연결 오류는 중요하다. `ConnectExceptionV3`가 발생하면 메시지를 명확하게 남기자.
- 예)`[연결 오류] 주소: . . .`
- `NetworkClientV3`을 사용하면서 발생하는 나머지 예외(`NetworkClientV3`의 자식)는 단순하게 출력하자.
- 예)`[네트워크 오류] 메시지: . . .`
- 그 외에 예외가 발생하면 다음과 같이 출력하자.
- 예)`[알 수 없는 오류] 메시지: . . .`
public class NetworkServiceV3_2 {
public void sendMessage(String data) {
String address = "http://example.com";
NetworkClientV3 client = new NetworkClientV3(address);
client.initError(data); //추가
try {
client.connect();
client.send(data);
} catch (ConnectExceptionV3 e) {
System.out.println("[연결 오류] 주소: " + e.getAddress() + ", 메시지: " + e.getMessage());
} catch (NetworkClientExceptionV3 e) {
System.out.println("[네트워크 오류] 메시지: " + e.getMessage());
} catch (Exception e) {
System.out.println("[알 수 없는 오류] 메시지: " + e.getMessage());
} finally {
client.disconnect();
}
}
}
`catch`는 순서대로 작동한다.
ConnectExceptionV3 발생
try {
// 1. ConnectExceptionV3 발생
} catch (ConnectExceptionV3 e) { // 2. 잡아서 처리
} catch (NetworkClientExceptionV3 e) {
} catch (Exception e) {
}
SendExceptionV3 발생
try {
// 1. SendExceptionV3 발생
} catch (ConnectExceptionV3 e) { // 2. 대상이 다름
} catch (NetworkClientExceptionV3 e) { // 3.NetworkClientExceptionV3은 부모이므로 여기서 잡음
} catch (Exception e) {
}
RuntimeException 발생
try {
// 1. RuntimeException 발생
} catch (ConnectExceptionV3 e) { // 2. 대상이 다름
} catch (NetworkClientExceptionV3 e) { // 3.대상이 다름
} catch (Exception e) { // 4.Exception은 RuntimeException의 부모이므로 여기서 잡음
}
- 모든 예외를 잡아서 처리하려면 마지막에 `Exception`을 두면 된다.
예외가 발생했을 때 `catch`를 순서대로 실행하므로, 더 디테일한 자식을 먼저 잡아야 한다.
실무 예외 처리
처리할 수 없는 예외
- 시스템 오류 때문에 발생한 예외들은 대부분 예외를 잡아도 해결할 수 있는 것이 없다. 예외를 잡아서 다시 호출을 시도해도 같은 오류가 반복될 뿐이다. 이런 경우 고객에게는 "현재 시스템에 문제가 있습니다."라는 오류 메시지를 보여주고, 내부 개발자가 빠르게 인지할 수 있도록 오류에 대한 로그를 남겨두어야 한다.
체크 예외의 부담
- 실무에서는 수 많은 라이브러리를 사용하고, 또 다양한 외부 시스템과 연동한다. 사용하는 각각의 클래스들이 자신만의 예외를 전달한다고 가정하자.
- 이 경우 `Service`는 호출하는 곳에서 던지는 체크 예외들을 처리해야 한다. 만약 처리할 수 없으면 밖으로 던져야 한다.
- 하지만 상대 네트워크 서버가 내려갔거나, 데이터베이스 서버에 문제가 발생한 경우 `Service`에서 복구할 수 없는 예외들이기 때문에 밖으로 던지는 것이 더 나은 결정이다.
class Service {
void sendMessage(String data) throws NetworkException, DatabaseException, ... {
...
}
}
- 이렇게 모든 체크 예외를 하나씩 다 밖으로 던져야 한다.
- 결국 중간에 모든 클래스에서 예외를 계속 밖으로 던지는 지저분한 코드가 만들어진다.
언체크 예외 사용
- `NetworkException`, `DatabaseException`은 잡아도 복구할 수 없다. 언체크 예외이므로 이런 경우 무시하면 된다.
- 사용하는 라이브러리가 늘어나서 언체크 예외가 늘어도 본인이 필요한 예외만 잡으면 되고, `throws`를 늘리지 않아도 된다.
처리할 수 없는 예외들은 중간에 여러 곳에서 나누어 처리하기 보다는 예외를 공통으로 처리할 수 있는 곳을 만들어서 한 곳에서 해결하면 된다.
public class NetworkClientV4 {
private final String address;
public boolean connectError;
public boolean sendError;
public NetworkClientV4(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 + " 서버에 데이터 전송 실패: " + data);
}
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;
}
}
}
- 언체크 예외이므로 `throws`를 사용하지 않는다.
public class NetworkServiceV4 {
public void sendMessage(String data) {
String address = "http://example.com";
NetworkClientV4 client = new NetworkClientV4(address);
client.initError(data);
try {
client.connect();
client.send(data);
} finally {
client.disconnect();
}
}
}
- `NetworkServiceV4`는 발생하는 예외인 `ConnetExceptionV4`, `SendExceptionV4`를 잡아도 해당 오류를 복구할 수 없다. 따라서 예외를 밖으로 던진다. (해결할 수 없는 예외는 다른 곳에서 공통으로 처리한다.)
- 언체크 예외이므로 `throws`를 사용하지 않는다.
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) {
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());
}
}
}
try-with resources
애플리케이션에서 외부 자원을 사용하는 경우 반드시 외부 자원을 해제해야 한다. 따라서 `finally` 구문을 반드시 사용해야 한다.
try {
//정상 흐름
} catch {
//예외 흐름
} finally {
//반드시 호출해야 하는 마무리 흐름
}
`try`에서 외부 자원을 사용하고, `try`가 끝나면 외부 자원을 반납하는 패턴이 반복되면서 자바에서는 Try with resources라는 편의 기능을 자바 7에서 도입했다.
이 기능을 사용하려면 `AutoCloseable` 인터페이스를 구현해야 한다.
package java.lang;
public interface AutoCloseable {
void close() throws Exception;
}
이 인터페이스를 구현하면 Try with resources를 사용할 때 `try`가 끝나는 시점에 `close()`가 자동으로 호출된다.
그리고 다음과 같이 Try with resources 구문을 사용하면 된다.
try (Resource resource = new Resource()) {
// 리소스를 사용하는 코드
}
public class NetworkClientV5 implements 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 + " 서버에 데이터 전송 실패: " + data);
}
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() {
System.out.println("NetworkClientV5.close");
disconnect();
}
}
- `implements AutoCloseable`을 통해 `AutoCloseable`을 구현한다.
- close(): `AutoCloseable` 인터페이스가 제공하는 이 메서드는 `try`가 끝나면 자동으로 호출된다. 종료 시점에 자원을 반납하는 방법을 여기에 정의하면 된다.
public class NetworkServiceV5 {
public void sendMessage(String data) {
String address = "http://example.com";
try (NetworkClientV5 client = new NetworkClientV5(address)) {
client.initError(data); //추가
client.connect();
client.send(data);
} catch (Exception e) {
System.out.println("[예외 확인]: " + e.getMessage());
throw e;
}
}
}
- Try with resources 구문은 `try` 괄호 안에 사용할 자원을 명시한다.
- 이 자원은 `try` 블록이 끝나면 자동으로 `AutoCloseable.close()`를 호출해서 자원을 해제한다.
- `catch` 블록 없이 `try` 블록만 있어도 `close()`는 호출된다.
Try with resources 장점
- 리소스 누수 방지: 모든 리소스가 제대로 닫히도록 보장한다. 실수로 `finally` 블럭을 적지 않거나, `finally` 블럭 안에서 자원 해제 코드를 누락하는 문제를 예방할 수 있다.
- 코드 간결성 및 가독성 향상: 명시적인 `close()` 호출이 필요없어 코드가 더 간결하고 읽기 쉬워진다.
- 스코프 범위 한정: 리소스로 사용되는 `client` 변수의 스코프가 `try` 블럭 안으로 한정된다. 따라서 코드 유지보수가 더 쉬워진다.
- 조금 더 빠른 자원 해제: 기존에는 try → catch → finally로 catch 이후에 자원을 반납했다. Try with resources 구문은 `try` 블록이 끝나면 즉시 `close()`를 호출한다.
'Java' 카테고리의 다른 글
[Java/김영한] 중첩 클래스, 내부 클래스 (0) | 2025.05.01 |
---|---|
[Java/김영한] 날짜와 시간 (0) | 2025.04.24 |
[Java/김영한] 열거형 - ENUM (1) | 2025.04.23 |
[Java/김영한] 래퍼, Class 클래스 (0) | 2025.03.23 |
[Java/김영한] String 클래스 (0) | 2025.03.22 |