프로그램 구성도 설명
NetworkClient
사용법connect()
: 호출 후 서버와 연결send(data)
: 연결된 서버에 메시지 전송disconnect()
: 연결 해제NetworkClient
사용시 주의사항connect()
가 실패한 경우 send()
를 호출하면 안 된다.disconnect()
를 호출해서 연결을 해제connect()
, send()
호출에 오류가 있어도 disconnect()
는 반드시 호출해야 한다.프로그램 흐름
사용자로부터의 입력 → 네트워크 서비스의sendMessage()
메서드 실행 → 네트워크 클라이언트 생성 후 특정 주소로의connect()
,send()
,disconnect()
메서드 실행
오류 상황 시뮬레이션
error1
이 있으면 연결에 실패한다. 이 때 오류 코드는 connectError
이다.error2
가 있으면 데이터 전송에 실패한다. 이 때 오류 코드는 sendError
이다.public class NetworkServiceV1_1 {
public void sendMessage(String data) {
String address = "http://example.com";
NetworkClientV1 networkClientV1 = new NetworkClientV1(address);
networkClientV1.initError(data);
networkClientV1.connect();
networkClientV1.send(data);
networkClientV1.disconnect();
}
}
public class NetworkClientV1 {
private final String address;
public boolean connectError;
public boolean sendError;
public NetworkClientV1(String address) {
this.address = address;
}
public String connect() {
if (connectError) {
System.out.println(address + " 서버 연결 실패");
return "connectError";
}
System.out.println(address + " 서버 연결 성공");
return "success";
}
public String send(String data) {
if (sendError) {
System.out.println(address + " 서버에 데이터 전송 실패: " + data);
return "sendError";
}
System.out.println(address + " 서버에 데이터 전송 성공: " + data);
return "success";
}
public void disconnect() {
System.out.println(address + " 서버 연결 해제");
}
// 사용자가 입력한 데이터를 기반으로 오류 체크
public void initError(String data) {
if (data.equals("error1")) {
connectError = true;
}
if (data.equals("error2")) {
sendError = true;
}
}
}
public class MainV1 {
public static void main(String[] args) {
NetworkServiceV1_1 networkServiceV1 = new NetworkServiceV1_1();
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.print("전송할 문자: ");
String string = scanner.nextLine();
if (string.equals("exit")) {
break;
}
networkServiceV1.sendMessage(string);
System.out.println();
}
System.out.println("프로그램 정상 종료");
}
}
문제점
error1
→ 연결에 실패했다면 서버로 데이터 전송이 이뤄질 수 없는데 서버로 데이터 전송이 이루어지고 있다는 문제문제 해결 주요 내용
public class NetworkServiceV1_2 {
public void sendMessage(String data) {
String address = "http://example.com";
NetworkClientV1 networkClientV1 = new NetworkClientV1(address);
networkClientV1.initError(data);
String connect = networkClientV1.connect();
if (isSuccess(connect)) {
System.out.println("[네트워크 오류 발생] 오류 코드 : " + connect);
return;
}
String send = networkClientV1.send(data);
if (isSuccess(send)) {
System.out.println("[데이터 전송 오류 발생] 오류 코드 : " + send);
return;
}
networkClientV1.disconnect();
}
private static boolean isSuccess(String result) {
return !result.equals("success");
}
}
정리
connect()
가 실패한 경우 : 데이터 전송이 일어날 수 없음(해결)connect()
, send()
모두 호출에 오류가 있는 경우 : disconnect()
를 호출하지 않는 문제 발생 → 연결을 해제하지 않으면 네트워크 연결 자원이 고갈될 수 있다.❗참고: 자바의 경우 GC가 있기 때문에 JVM 메모리에 있는 인스턴스는 자동으로 해제할 수 있다. 하지만 외부 연결과 같은 자바 외부 자원은 자동 해제가 되지 않는다. 따라서 외부 자원을 사용한 경우에는 반드시 연결을 해제해서 외부 자원을 반납해야 한다.
public class NetworkServiceV1_3 {
public void sendMessage(String data) {
String address = "http://example.com";
NetworkClientV1 networkClientV1 = new NetworkClientV1(address);
networkClientV1.initError(data);
String connect = networkClientV1.connect();
if (isSuccess(connect)) {
System.out.println("[네트워크 오류 발생] 오류 코드 : " + connect);
} else {
String send = networkClientV1.send(data);
if (isSuccess(send)) {
System.out.println("[데이터 전송 오류 발생] 오류 코드 : " + send);
}
}
networkClientV1.disconnect();
}
private static boolean isSuccess(String result) {
return !result.equals("success");
}
}
정리
connect()
가 실패한 경우 : 데이터 전송이 일어날 수 없음(해결)connect()
, send()
모두 호출에 오류가 있는 경우 : disconnect()
를 마지막에 무조건 호출하여 연결 해제(해결)Object
: 자바에서 기본형을 제외한 모든 것은 객체다. 예외도 객체다. 모든 객체 최상위 부모는 Object
이므로 예외의 최상위 부모도 Object
이다.Throwable
: 최상위 예외, 하위에 Exception
과 Error
가 있다.Error
: 메모리 부족이나 심각한 시스템 오류와 같이 애플리케이션에서 복구가 불가능한 시스템 예외이다. 애플리케이션 개발자는 이 예외를 잡으려고 해선 안 된다.Exception
: 체크 예외, 애플리케이션 로직에서 실질적인 최상위 예외, Exception
과 그 하위 예외는 모두 컴파일러가 체크하는 체크 예외인데 이 때, RuntimeException
은 예외로 한다.RuntimeException
: 언체크 예외, 컴파일러가 체크하지 않는 예외이다. RuntimeException
과 그 자식 예외는 모두 언체크 예외이다.체크 예외 vs 언체크 예외
체크 예외는 발생한 예외를 개발자가 명시적으로 처리해야 한다. 그렇지 않으면 컴파일 오류가 발생한다. 언체크 예외는 개발자가 발생한 예외를 명시적으로 처리하지 않아도 된다.
상속 관계에서 부모 타입은 자식을 담는다. 상위 예외를 잡으면 그 하위 예외까지 모두 잡는다. 따라서 애플리케이션 로직에서는 Throwable
예외를 잡으면 안되는데, 앞서 이야기한 잡으면 안되는 Error
예외도 함께 잡을 수 있기 때문이다. 애플리케이션 로직은 이런 이유로 Exception
부터 끝나고 예외로 생각하고 잡으면 된다.
예외를 처리하지 못하겠다면 자신을 호출한 곳으로 예외를 던져야 한다.
catch
키워드를 사용하면 예외를 잡고 throws
키워드를 사용하면 예외를 자신을 호출한 쪽에 넘긴다.throws
를 사용해 계속 예외를 던지다가 Main
에서 해결하지못하고 밖으로 던지면 예외 로그를 출력하면서 시스템이 종료된다.public class MyCheckedException extends Exception {
public MyCheckedException() {
super();
}
public MyCheckedException(String message) {
super(message);
}
public MyCheckedException(String message, Throwable cause) {
super(message, cause);
}
public MyCheckedException(Throwable cause) {
super(cause);
}
protected MyCheckedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
예외 객체
throw
예외는 새로운 예외를 발생시킬 수 있다. 예외도 객체이기 때문에 객체를 먼저 new
로 생성하고 예외를 발생시켜야 한다.throws
키워드는 예외를 메서드 밖으로 던질 때 사용하는 키워드이다.참고 : 체크 예외를 밖으로 던지는 경우에도 해당 타입과 그 하위 타입 모두 던질 수 있다.
throws
에 MyCheckedException
의 상위 타입인 Exception
을 적어주어도 MyCheckedException
을 던질 수 있다.public class Client {
public void call() throws MyCheckedException {
throw new MyCheckedException("ex");
}
}
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) throws MyCheckedException {
Service service = new Service();
service.callCatch();
System.out.println("정상 종료");
}
}
실행 결과
예외처리, message = ex
정상 흐름 반환
정상 종료
❗예외를 잡지 않고 밖으로 던진 경우
public class CheckedCatchMain {
public static void main(String[] args) throws MyCheckedException {
Service service = new Service();
service.catchThrow();
System.out.println("정상 종료");
}
}
실행 결과
Exception in thread "main" ~~.exception.MyCheckedException: ex
RuntimeException
과 그 하위 예외는 언체크 예외로 분류된다. 언체크 예외는 말 그대로 컴파일러가 예외를 체크하지 않는다는 뜻이다.
public class MyUncheckedException extends RuntimeException {
public MyUncheckedException() {
super();
}
public MyUncheckedException(String message) {
super(message);
}
public MyUncheckedException(String message, Throwable cause) {
super(message, cause);
}
public MyUncheckedException(Throwable cause) {
super(cause);
}
protected MyUncheckedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
public class Client {
public void call() {
throw new MyUncheckedException("ex");
}
}
public class Service {
Client client = new Client();
public void callCatch() {
try {
client.call();
} catch (RuntimeException e) {
System.out.println("예외처리, message = " + e.getMessage());
}
System.out.println("정상 흐름 반환");
}
public void callThrow() {
client.call();
}
}
public class UnCheckedCatchMain {
public static void main(String[] args) {
Service service = new Service();
service.callThrow();
System.out.println("정상 종료");
}
}
언체크 예외를 밖으로 던지는 코드 - 선언(생략 가능)
public class Client {
public void call() throws MyUncheckedException {
throw new MyUncheckedException("ex");
}
}
throws
예외를 선언해도 된다. 물론 생략할 수 있다.throws
를 선언해야 하지만 언체크 예외는 이 부분을 생략할 수 있다.