JAVA

[Java] 예외 처리

songsua 2025. 2. 16. 18:54

실제 사용할 코드

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가 없어서 정상출력안된다.

정리

체크 예외와 언체크 예외의 차이는 예외를 처리할  수 없을 때 예외를 밖으로 던지는 부분에 있다.

이부분을 필수로 선언해야 하는가 생략할 수 있는가의 차이이다.

'JAVA' 카테고리의 다른 글

[Java] 익명 클래스  (0) 2025.02.16
[Java] 지역 클래스  (0) 2025.02.15
[Java] Thread  (2) 2025.02.11
[Java] 제네릭  (0) 2025.02.09
[Java] 중첩 클래스, 내부 클래스  (0) 2025.02.09