[Java] Level3 계산기 구현

thezz9·2025년 3월 4일
0

저번에 만들었던 Level2 계산기 로직을 리팩토링해서 Level3 계산기를 구현했다.

Github

요구사항에 맞게 추가한 기능은 이렇다

1️⃣ Enum으로 연산자 정보 관리
2️⃣ Generic을 적용해 피연산자를 여러 타입으로 받을 수 있도록 확장
3️⃣ Lambda & Stream을 적용해 입력한 값보다 큰 연산 결과만 출력


1. 연산자 Enum 적용

package level3;

public enum OperatorType {
    SUM('+'), SUB('-'), MUL('*'), DIV('/');

    private final char operator;

    OperatorType(char operator) {
        this.operator = operator;
    }

    public char getOperator() {
        return operator;
    }

    /**
     * 잘못된 연산자 예외 처리
     */
    public static OperatorType verifyOperator(char operator) {
        for (OperatorType op : values()) {
            if (op.operator == operator) {
                return op;
            }
        }
        throw new IllegalArgumentException();
    }
}

Enum 안에서 연산 로직을 구현하는 방법도 고려했으나, 그렇게 하면 OperatorType의 역할이 단순한 연산자 정보 저장이 아니라 계산까지 담당하게 되어 책임이 불필요하게 커질 위험이 있었다.
따라서 연산 로직은 별도의 클래스에서 처리하고, Enum은 연산자 정보를 관리하는 역할로만 활용했다.


2. 제네릭을 적용해 addNum() 메서드 기능 확장

package level3;

import java.util.Scanner;

public class Input {

    private final Filter filter = new Filter();
    private final Scanner sc = new Scanner(System.in);

    /**
     * 숫자 입력 메서드
     */
    public <T extends Number> T addNum(String order) {
        // 필터링 구간
        System.out.print(order + " 숫자를 입력하세요: ");
        return filter.checkedPositiveNum();
    }

    /**
     * 연산자 입력 메서드
     */
    public OperatorType addOperator() {
        char operator;
        while (true) {
            System.out.print("사칙연산 기호를 입력하세요: ");
            operator = sc.next().charAt(0);
            try {
                return OperatorType.verifyOperator(operator);
            } catch (IllegalArgumentException e) {
                System.out.println("잘못된 연산자입니다. 사칙연산 기호를 다시 입력하세요.");
            }
        }

    }

}
package level3;

import java.util.Scanner;

public class Filter {

    Scanner sc = new Scanner(System.in);

    /**
     * 필터링 메서드 - 입력값이 정수인지 실수인지 판별하고, 문자열 입력을 방지
     */
    @SuppressWarnings("unchecked")
    public <T extends Number> T checkedPositiveNum() {
        while (true) {
            if (sc.hasNextInt()) {
                System.out.println("정수");
                int num = sc.nextInt();
                return (T) Integer.valueOf(num);
            } else if (sc.hasNextDouble()) {
                System.out.println("실수");
                double num = sc.nextDouble();
                return (T) Double.valueOf(num);
            } else {
                System.out.println("올바른 숫자를 입력하세요.");
                sc.next();
            }
        }
    }

    /**
     * 나눗셈 검증 메서드 - 분모에 0이나 음수 X
     */
    public <T extends Number> T checkedDivide(T secondNum, OperatorType operator) {
        while (operator == OperatorType.DIV && secondNum.doubleValue() <= 0) {
            System.out.println("나눗셈 연산에서 분모(두 번째 숫자)에 0이나 음수가 올 수 없습니다. 다시 입력하세요.");
            secondNum = checkedPositiveNum();
        }
        return secondNum;
    }

}

1) 숫자 입력 메서드

  • Generic 적용

2) 연산자 입력 메서드

  • Enum에 선언된 상수 사용
  • try-catch 구문을 사용해 연산자 일치 여부 확인 및 검증 수행

3) 필터링 메서드

  • Generic 적용
  • 입력 값에 따라 정수, 실수 판별
  • 음수도 받을 수 있게 양수 필터링 로직 제거

4) 나눗셈 검증 메서드

  • Generic 적용
  • 나눗셈 연산에서 분모에 0이나 음수가 올 수 없게 필터링
  • 연산자를 입력한 뒤 두 번째 숫자를 입력하는 것이므로 조건이 맞는 수가
    입력될 때까지 반복

3. Lambda & Stream을 적용한 필터링 메서드

	case "2":
    	System.out.print("기준 값을 입력하세요: ");
        double inputValue = sc.nextDouble();
        System.out.println("저장된 연산 결과: " + calc.filteredResult(inputValue));
        continue;
    /**
     * 연산 결과 필터링 메서드 - 입력 값보다 큰 연산 결과만 출력
     */
    public List<Double> filteredResult(double inputValue) {
        return getResults().stream()
                .filter(result -> result > inputValue)
                .collect(Collectors.toList());
    }

메뉴에서 2번 입력 후 기준 값을 입력할 시 현재 저장된 연산 결과 중 기준값보다 큰 값만 출력


트러블슈팅

문제

트러블슈팅이라고 하기엔 애매하지만 Generic의 활용이 애매하게 느껴졌다. 기존의 코드에선

public <T extends Number> T checkedPositiveNum(Class<T> type)

파라미터에 클래스 타입 변수를 받도록 선언했었는데, 이렇게 하니 메서드 호출 시

checkedPositiveNum(Integer.class)
checkedPositiveNum(Double.class)

이런 식으로 호출했어야 했고, if-else 조건문 어느 구간에 들어가는지 확인 해보니 정수, 실수의 입력에 상관없이 명시된 클래스 타입의 조건문 구간으로만 들어가는 것이였다.

해결 과정

Class<T>를 파라미터로 받도록 하면, 호출할 때 명시적으로 타입을 지정해야 한다.
하지만 이렇게 하면 입력값의 실제 타입과 무관하게 호출된 타입으로만 처리되므로, Generic의 장점인 '입력값에 따라 유연하게 타입을 결정하는 기능'이 사라진다.
따라서 클래스 타입을 받지 않고 입력값을 동적으로 판별하는 본문 3에 나온 방식으로 개선했다.

느낀점

아직도 어떤 방법이 정답인지는 모르겠다. Generic의 기준이 어디까지인지 감이 잘 안 온다.
튜터님께 여쭤보니 내가 구현한 방식이 맞다고 하셔서 고민은 해결됐었다.
연산 과정까지 Generic을 유지하는 방법도 고려했지만, 결국 최종 결과를 출력할 때는 모든 숫자를 double로 변환해야 한다. 그렇다면 연산 결과를 Generic으로 유지하는 의미가 크지 않다고 판단해, 연산 후 double 값을 반환하는 방식으로 정했다.
내가 구현한 Generic이 올바르게 작성된 것인지 확신하지 못하는 개발자가 아니라
나도 튜터님처럼 확실히 알고 명확한 답을 줄 수 있는 개발자가 되어야겠다.

profile
개발 취준생

0개의 댓글