반응형

 

 

 

프로그램을 작성하다 보면

예외를 처리한 후, 예외가 발생된 메서드를 다시 실행해야하는 경우가 있습니다.

그것을 수행하는 몇 가지 방법에 대해 정리해보고자 합니다.

 

예외 처리의 기본적인 원리나,

예외 처리 후 프로그램을 종료하는 로직에 대한 설명은

아래 아래클을 참고해주세요.

 

[Java] 간단한 예외처리(try catch) 원리 (feat. throws IllegalArgumentException은 왜 빨간 줄이 안 생길까?)

예외를 처리하다 보면, 아래와 같은 에러 문구를 쉽게 볼 수 있습니다. 바로 Unhandled exception 입니다. 예외 발생 예외의 종류는 다양하고, 예외가 발생하는 코드도 다양합니다. 본 아티클에서는,

woojin.tistory.com

 

 

 

 


문제 상황 1번 - 예외 처리 후 재실행

비즈니스 요구사항
1. 숫자를 입력 받고, 2를 곱한 수를 출력한다.
2. 숫자가 아닌 문자를 입력 받으면 [ERROR]로 시작하는 에러 메시지를 출력하고, 다시 입력 받는다.
// main 메서드를 호출하는 Practice 클래스
public class Practice {
    public static void main(String[] args) {
        Study study = new Study();
        study.multiple();
    }
}


// 비즈니스 로직을 구현한 Study 클래스
public class Study {
    public void multiple() {
        int number = readNumber();
        System.out.printf("%d에 2를 곱하면 %d 입니다.", number, number*2);
    }

    private int readNumber() {
        System.out.print("숫자를 입력해주세요 : ");
        Scanner scanner = new Scanner(System.in);
        int number = scanner.nextInt();
        return number;
    }
}

위의 코드는, 숫자를 입력 받아 2를 곱한 출력을 보여주는 로직을 구현한 것입니다.

출력은 아래 사진과 같습니다.

 

그런데, 숫자가 아닌 문자를 입력한다면 어떻게 될까요?

InputMismatchException이 발생하고 프로그램이 종료됩니다.

 

비즈니스 요구사항을 만족하려면

에러 메시지를 출력하고, 입력을 다시 받아야 합니다.

아래와 같이 코드를 수정하겠습니다.

 

// 재귀적 호출
public class Study {
    public void multiple() {
        try {
            int number = readNumber();	// 1번
            System.out.printf("%d에 2를 곱하면 %d 입니다.", number, number*2);	// 2번
        } catch (InputMismatchException e) {	// 3번
            System.out.println("[ERROR] 숫자를 입력해주세요.");	// 4번
            multiple();	// 5번
        }
    }

    private int readNumber() {
        System.out.print("숫자를 입력해주세요 : ");
        Scanner scanner = new Scanner(System.in);
        int number = scanner.nextInt();
        return number;
    }
}

예외는 readNumber()에서 발생합니다.

그것을 호출하는 multiple()try-catch를 적용하고,

재귀적으로 multiple()을 다시 호출했습니다.

 

즉, 1번에서 예외가 발생하면

2번을 건너뛰고

3번에서 예외를 catch합니다.

그리고 4번, 5번 문장이 실행됩니다.

 

하지만 재귀적 호출 방식에는 치명적인 단점이 있습니다.

메모리에 스택 방식으로 메서드 호출 명령이 저장되지만,

호출된 메서드가 종료되지 않으면 메모리에 명령이 계속 쌓이게 되어

스택오버플로우(SOF) 현상이 발생할 수 있습니다.

 

사실 위의 예시 코드는 재귀적 호출 시 별도의 연산을 하지 않고,

함수를 호출하기만 하는 방식입니다.

그래서 SOF의 부담이 적기는 하지만, 호출된 깊이만큼 메모리에 스택이 쌓이는 것은 마찬가지입니다.

호출되는 로직이 변경되는 경우도 있을 수 있습니다.

 

따라서 재귀적 호출의 위험을 없애기 위해,

반복문을 통해서도 문제를 해결할 수 있습니다.

// 반복문 사용
public class Study {
    public void multiple() {
        while (true) {
            try {
                int number = readNumber();
                System.out.printf("%d에 2를 곱하면 %d 입니다.", number, number * 2);
                break;
            } catch (InputMismatchException e) {
                System.out.println("[ERROR] 숫자를 입력해주세요.");
            }
        }
    }

    private int readNumber() {
        System.out.print("숫자를 입력해주세요 : ");
        Scanner scanner = new Scanner(System.in);
        int number = scanner.nextInt();
        return number;
    }
}

catch문에서 multiple()을 다시 호출하지 않고,

try문이 온전히 실행되어 break 될 때까지

try-catch문을 반복합니다.

 

 

multiple()이 아닌 readNumber()에서 try-catch를 할 수도 있습니다.

 

다만, readNumber()의 반환 타입은 int이기 때문에

catch문에서는 두 가지 중 하나를 실행해야 합니다.

1. int를 반환한다.

2. Throwble 객체를 throw 한다.

 

입력을 다시 받지 않고, catch 문에서 int를 반환하는 것은 어색합니다.

Throwble 객체를 throw한다면, 결국 상위 메서드에서 try-catch를 통해 예외를 처리해야 합니다.

따라서 readNumber()가 아닌 상위 메서드 multiple()에서 예외 처리를 진행했습니다.

 

 

 

 

 

문제 상황 2번 - 특정 예외를 발생시킨 후 재실행

비즈니스 요구사항
1. 숫자를 입력 받고, 2를 곱한 수를 출력한다.
2. 숫자가 아닌 문자를 입력 받으면 IllegalArgumentException을 발생시키고, [ERROR]로 시작하는 에러 메시지를 출력한 후, 다시 입력 받는다.

이번에는 특정 예외를 발생하라는 요구사항이 추가되었습니다.

기존에는 readNumber()scanner.nextInt()라는 구문에서 InputMismatchException이 발생했습니다.

이번에는 이를 IllegalArgumentException으로 바꿔서 예외를 발생시켜보겠습니다.

 

// 재귀적 호출
public class Study {
    public void multiple() {
        try {
            int number = readNumber();
            System.out.printf("%d에 2를 곱하면 %d 입니다.", number, number * 2);
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage());
            multiple();
        }
    }

    private int readNumber() {
        try {
            System.out.print("숫자를 입력해주세요 : ");
            Scanner scanner = new Scanner(System.in);
            int number = scanner.nextInt();
            return number;
        } catch (InputMismatchException e) {
            throw new IllegalArgumentException("[ERROR] 숫자를 입력해주세요.");
        }

    }
}

이번에는 readNumber() 내부에서 발생하는 InputMismatchExceptioncatch한 후 IllegalArgumentException을 발생시켰습니다.

그리고 상위 메서드인 multiple에서 그것을 catch한 후, 다시 multiple을 재귀적으로 호출합니다.

 

 

이 또한, 재귀적 호출로 인한 SOF의 위험에서 벗어나기 위해

반복문을 사용할 수 있습니다!

 

위의 문제 상황 1번을 참고하여, 직접 반복문으로 수정해보신다면

예외 처리에 대한 이해도가 높아지시리라 생각됩니다..만

 

아래에 반복문으로 수정한 코드를 공유드립니다.

 

 

// 반복문 사용
public class Study {
    public void multiple() {
        while (true) {
            try {
                int number = readNumber();
                System.out.printf("%d에 2를 곱하면 %d 입니다.", number, number * 2);
                break;
            } catch (IllegalArgumentException e) {
                System.out.println("[ERROR] 숫자를 입력해주세요.");
            }
        }
    }

    private int readNumber() {
        try {
            System.out.print("숫자를 입력해주세요 : ");
            Scanner scanner = new Scanner(System.in);
            int number = scanner.nextInt();
            return number;
        } catch (InputMismatchException e) {
            throw new IllegalArgumentException("[ERROR] 숫자를 입력해주세요.");
        }

    }
}

 

 

 

 

 


이렇게 예외가 발생했을 때, 예외가 발생한 메서드를 반복하는 로직을 구현해보았습니다.

 

1. 재귀적 호출

2. 반복문 사용

 

위의 두가지 방법으로 로직을 구현할 수 있었는데,

각각의 장단점이 있습니다.

 

재귀적인 호출은 반복문을 사용하지 않아 들여쓰기가 깊어지지 않는다는 장점이 있습니다.

또한 메모리에 명령 호출 스택이 쌓여, SOF(Stack OverFlow)가 발생할 수 있습니다.

반복문은 들여쓰기가 깊어지지만, 메서드 내부에서 반복을 원하는 부분만 반복할 수 있습니다.

 

가독성은 재귀적 호출이, 성능은 반복문 사용이 낫다고 볼 수 있습니다.

 

 

 

우리 인생도 수많은 예외가 발생하곤 합니다.

능숙하게 처리하고 목표를 이뤄내는 법을 익혀야겠다는 생각을 합니다.

 

감사합니다.

 

 

 

 

 

 

 

 

 

반응형

+ Recent posts