[Java] 예외 처리
실제 사용할 코드
package exception.ex0;
import java.util.Scanner;
public class MainV0 {
public static void main(String[] args) {
NetworkServiceV0 networkServiceV0 = new NetworkServiceV0();
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("전송할 문자: ");
String input = scanner.nextLine();
if (input.equals("exit")) {
break;
}
networkServiceV0.setMessage(input);
System.out.println();
}
System.out.println("프로그램을 종료합니다.");
}
}
package exception.ex0;
public class NetworkClientV0 {
private final String address;
public NetworkClientV0(String address) {
this.address = address;
}
public String connect() {
System.out.println("서버 연결 성공" + address);
return "success";
}
public String send(String data) {
System.out.println( address + "서버에 데이터 전송 :" + data);
return "success";
}
public void disconnect() {
System.out.println(address + " 서버와 disconnect");
}
}
package exception.ex0;
public class NetworkServiceV0 {
public void setMessage(String data) {
String address = "http://example.com;";
NetworkClientV0 client = new NetworkClientV0(address);
client.connect();
client.send(data);
client.disconnect();
}
}
오류 상황을 시뮬레이션 하기 위해 사용자의 입력 값을 활용할 것이다.
- 연결 실패 : 사용자가 입력하는 문자에 "error1" 이라는 단어가 있으면 연결에 실패한다. 오류 코드는 "connectError" 이다.
- 전송 실패: 사용자가 입력하는 문자에 "error2" 이라는 단어가 있으면 데이터 전송에 실패한다. 오류 코드는 "sendError" 이다.
package exception.ex1;
import java.util.Scanner;
public class MainV1 {
public static void main(String[] args) {
NetworkServiceV1 networkServiceV0 = new NetworkServiceV1();
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("전송할 문자: ");
String input = scanner.nextLine();
if (input.equals("exit")) {
break;
}
networkServiceV0.setMessage(input);
System.out.println();
}
System.out.println("프로그램을 종료합니다.");
}
}
package exception.ex1;
public class NetworkClientV1 {
public boolean connectError;
public boolean sendError;
//field 추가
private final String address;
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 + "전송실패");
return "sendError";
}
System.out.println( address + "서버에 데이터 전송 :" + data);
return "success";
}
public void disconnect() {
System.out.println(address + " 서버와 disconnect");
}
public void initError(String data) {
//error1이라는 단어를 가지고 있으면 connectError가 활성화된다.
if (data.contains("error1")) {
connectError = true;
}
if (data.contains("error2")) {
sendError = true;
}
}
}
package exception.ex1;
public class NetworkServiceV1 {
public void setMessage(String data) {
String address = "http://example.com;";
NetworkClientV1 client = new NetworkClientV1(address);
client.initError(data);
client.connect();
client.send(data);
client.disconnect();
}
}
error01을 입력하면 오류가 발생했다고 호출하게 수정하였다.
하지만 연결이 실패하면 데이터 전송하지 않아야 하는데, 여기서는 데이터를 전송한다.
여기서 if,else 을 사용하면서 예외처리하는 것은 코드가 보기 불편해지고 길어진다.
이를 위해서 자바의 예외처리를 사용한다.
주요 키워드는 try,catch,finally,throw, throws 이다.
- Object : 자바에서 기본형을 제외한 모든 것은 객체이다. 예외도 객체이다. 모든 객체의 최상위 부모는 Object 이므로 예외의 최상위 부모도 Object이다.
- Throwable : 최상위 예외이다. 하위에 Exception과 Error 가 있다.
- Error : 메모리 부족이나 심각한 시스템 오류와 같이 애플리케이션에서 복구가 불가능한 시스템 예외이다.
- Exception : 체크 예외
- 애플리케이션 로직에서 사용할 수 있는 실질적인 최상위 예외이다
- Exception 과 그 하위 예외는 모두 컴파일러가 체크하는 체크 예외이다. 단, RuntimeException은 예외로 한다.
- RuntumeException : 언체크 예외, 런타임예외
- 컴파일러가 체크하지 않는 언체크 예외이다
- RuntimeException과 그 자식 예외는 모두 언체크 예외이다
- 체크 예외 vs 언체크 예외
체크 예외는 발생한 예외를 개발자가 명시적으로 처리해야 한다. 그렇지 않으면 컴파일 오류가 발생한다. 언체크 예외는 개발자가 발생한 예외를 명시적으로 처리하지 않아도 된다.
체크 예외 (Checked Exception) | 컴파일러가 반드시 예외 처리를 요구 | IOException, SQLException |
언체크 예외 (Unchecked Exception) | 컴파일 시 체크되지 않고, 실행 중에 발생 | NullPointerException, ArrayIndexOutOfBoundsException, IllegalArgumentException |
예외 처리 기본 규칙
1: `Main` 은 `Service` 를 호출한다.
2: `Service` 는 `Client` 를 호출한다.
3: `Client` 에서 예외가 발생했다.
4: `Client` 에서 예외를 처리하지 못하고 밖으로 던진다. 여기서 `Client` 의 밖은 `Client` 를 호출한
`Service` 를 뜻한다.
5: `Service` 에 예외가 전달된다. `Service` 에서 예외를 처리했다. 이후에는 애플리케이션 로직이 정상 흐름으 로 동작한다.
6: 정상 흐름을 반환한다.
예외를 처리하지 못한 경우, 결국 자신을 호출한 곳으로 예외를 던진다.
예외에 대해서 2가지 기본 규칙
1. 예외는 잡아서 처리하거나 밖으로 던져야 한다.
2. 예외를 잡거나 던질 때 지정한 예외뿐만 아니라 그 예외의 자식들도 함께 처리할 수 있다.
- catch 로 잡으면 하위 예외들도 모두 잡을 수 있다.
- throw 로 던지면 그 하위 예외들도 모두 던질 수 있다.
Exception과 그 하위 예외는 모두 컴파일러가 체크하는 체크 예외이다. 단 RuntimeException은 예외로 한다.
체크 예외는 잡아서 처리하거나, 또는 밖으로 던지도록 선언을 해줘야 한다. 그렇지 않으면 컴파일 오류가 발생한다.
체크예외
package exception.basic;
//Exception을 상속받은 예외는 체크 예외가 된다.
public class MyCheckedException extends Exception{
public MyCheckedException(String message) {
super(message);
}
}
예외를 상속받아야 한다.
package exception.basic;
public class Client {
public void call() throws MyCheckedException {
//throws 예외는 발생시킨 예외를 메서드 밖으로 던질 때 사용하는 키워드이다.
//문제가 발생할 경우
//throw 예외라고 하면 새로운 예외를 발생시킬 수 있다. 예외도 객체이기 때문에 객체를 먼저 new로 생성하고 예외를 발생시켜야 한다.
throw new MyCheckedException("ex");
}
}
throw 와 throws의 차이에 주의해야한다. throws는 "나 이거 해결못해!" , throw는 "이거 던질게"
package exception.basic;
public class Service {
Client client = new Client();
//예외를 잡아서 호출하는 코드 만들어보기
public void callCatch() {
try {
client.call(); //바로 throw 로 인해 밖으로 터진다.
} catch (MyCheckedException e) {
//catch는 잡다로, 예외라서 throw할 것을 잡겠다라는 의미
System.out.println("예외를 처리, message" + e.getMessage());
}
System.out.println("정상 흐름");
}
}
package exception.basic;
public class CheckedCatchMain {
public static void main(String[] args) {
Service service = new Service();
service.callCatch();
System.out.println("정상종료");
}
}
예외를 처리, message : ex
정상 흐름
정상종료
callCatch() --> Client의 call 호출하여 터짐 "ex" 를 내면서 터진다 --> try catch 에서 터진 것을 잡는다.
-->MyCheckedException e 에서 참조값을 가져온다
그러면서, e.getMessage(); 에서 호출 --> catch 되었으니 밑으로 정상적으로 수행
실행순서:
1. `main()`--> `service.callCatch()`-->`client.call()` **[예외발생,던짐]**
2. `main()`<--`service.callCatch()` **[예외처리]** <--`client.call()`
3. `main()` **[정상흐름]** <--`service.callCatch()` <--`client.call()`
만약 예외를 catch로 잡지 못할 경우에는 예외를 밖으로 던져야한다.
그러면, MyCheckedException 가 exception의 상속을 받은 자식이엿는데, 그냥 부모인 exception을 그대로 사용하면 어떻게 될까?
답 : 사용 가능하다.
언체크 예외
RuntimeException 과 그 하위 예외는 언체크 예외로 분류된다
언체크 예외는 말 그대로 컴파일러가 예외를 체크하지 않는다는 뜻이다.
언체크 예외는 체크 예외와 기본적으로 동일하다. 차이가 있다면 예외를 던지는 throw를 선언하지 않고 생략할 수 있다.
생략한 경우 자동으로 예외를 던진다.
package exception.unchecked;
// RuntimeException 을 상속받은 예외는 언체크 예외가 된다.
public class MyUncheckedException extends RuntimeException {
public MyUncheckedException(String message) {
super(message);
}
}
package exception.unchecked;
public class Client {
public void call() {
throw new MyUncheckedException("ex");
}
}
체크 예외의 경우
package exception.basic;
public class Client {
public void call() throws MyCheckedException {
//throws 예외는 발생시킨 예외를 메서드 밖으로 던질 때 사용하는 키워드이다.
//문제가 발생할 경우
//throw 예외라고 하면 새로운 예외를 발생시킬 수 있다. 예외도 객체이기 때문에 객체를 먼저 new로 생성하고 예외를 발생시켜야 한다.
throw new MyCheckedException("ex");
}
}
throws 로 어디에 던질 것인지를 명시해 줘야 했었는데..
언체크는 그냥 어디에 던지지 않고 밖으로 나가버린다. 컴파일러가 체크하지 않고 지나간다.
package exception.unchecked;
public class Service {
Client client = new Client();
//필요한 경우 예외를 잡아서 처리할 수 있다.
public void callcatch() {
try {
client.call();
} catch (MyUncheckedException e) {
System.out.println("예외처리 :" + e.getMessage());
}
System.out.println("정상 로직");
}
public void callthrows() {
client.call();
}
}
package exception.unchecked;
public class UncheckedMain {
public static void main(String[] args) {
Service service = new Service();
service.callcatch();
System.out.println("정상 로직");
service.callthrows();
}
}
예외처리 :ex
정상 로직
정상 로직
Exception in thread "main" exception.unchecked.MyUncheckedException: ex
at exception.unchecked.Client.call(Client.java:5)
at exception.unchecked.Service.callthrows(Service.java:17)
at exception.unchecked.UncheckedMain.main(UncheckedMain.java:9)
callthrows() 메서드는 catch가 없어서 정상출력안된다.
정리
체크 예외와 언체크 예외의 차이는 예외를 처리할 수 없을 때 예외를 밖으로 던지는 부분에 있다.
이부분을 필수로 선언해야 하는가 생략할 수 있는가의 차이이다.