참고 코드


console-calculator/src/main/java/org/example/lv3 at main · BibliyaSeo/console-calculator

문제 사항 및 수정 사항


  1. Enum 사용

    <aside> 📌

    처음엔 Enum에서 추상메서드를 이용해서 계산을 바로 하려고 했다. 그러면 switch-case문을 이용하지 않아도 되어서 편하다고 생각했는데! int로 받을 때는 문제가 없었는데 제네릭을 이용해야 하기 때문에 Number를 사용해야 해서 계산할 때 모든 값에 .doubleValue(); 를 적어줘야 하는게 코드상 예뻐보이지 않고 새로운 연산을 추가할 수도 있으니 따로 분리해 두는 게 더 괜찮을 것이라 생각했다.

    </aside>

    기존 코드

    public enum OperatorType {
        ADD('+') {
            @Override
            public double calculate(Number a, Number b) {
                return a.doubleValue() + b.doubleValue();
            }
        },
        SUB('-') {
            @Override
            public double calculate(Number a, Number b) {
                return a.doubleValue() - b.doubleValue();
            }
        },
        MUL('*') {
            @Override
            public double calculate(Number a, Number b) {
                return a.doubleValue() * b.doubleValue();
            }
        },
        DIV('/') {
            @Override
            public double calculate(Number a, Number b) {
                return a.doubleValue() / b.doubleValue();
            }
        };
    
    		// 생략
        public abstract double calculate(Number a, Number b);
        // 생략
    }
    
    

    바꾼 코드

    public enum OperatorType {
        ADD('+'),
        SUB('-'),
        MUL('*'),
        DIV('/');
        
       // 생략
       // calculate 삭제
       // 생략
    }
    

    추가된 코드

    public class CalculatorUtil {
        public static double calculate(Number a, Number b, OperatorType operator) {
            double num1 = a.doubleValue();
            double num2 = b.doubleValue();
    
            return switch (operator) {
                case ADD -> num1 + num2;
                case SUB -> num1 - num2;
                case MUL -> num1 * num2;
                case DIV -> {
                    if (num2 == 0.0) {
                        throw new ArithmeticException("0으로 나눌 수 없습니다.");
                    }
                    yield num1 / num2;
                }
                default -> throw new IllegalArgumentException("지원하지 않는 연산자입니다.");
            };
        }
    }
    

    <aside> 📌

    일반 switch-case문을 작성했다가 노란색 줄이 뜨길래 확인해 봤더니 Java 14부터 도입된 enhanced switch expression 으로 간결하게 표시할 수 있다고 해서 변경해 봤다.

    </aside>

  2. Generic 사용

    // ArithmenticCalculator.java
    ArithmeticCalculator<T extends Number>
    

    Integer, Double, Float 등이 들어올 수 있도록 설정했다

    // Main.java
    calculateAdd(T num1, T num2, OperatorType operator)
    

    → 클래스 제네릭 타입 T를 사용해서 호출자가 지정한 타입과 맞춰서 입력받을 수 있게 설정했다

    // ArithmeticCalculator.java
    CalculatorUtil.calculate(num1, num2, operator)
    
    // CalculatorUtil.java
    public static double calculate(Number a, Number b, OperatorType operator)
    

    → Number로 받을 수 있게 설정하였다

    왜 Number로 받아? 여긴 왜 T로 안 받아?

    일단!

    <aside> 📌

    CalculatorUtil.calculate(T a, T b, OperatorType operator)처럼 메서드에 제네릭 타입 파라미터 T를 직접 선언하지 않은 상태에서 클래스나 메서드 레벨 제네릭 T를 사용하는 것은 불가능하다고 한다.

    CalculatorUtil 클래스나 calculate 메서드가 제네릭 타입 T를 선언하지 않았다면 사용할 수 없다!!

    </aside>

    public class CalculatorUtil {
        public static double calculate(T a, T b, OperatorType operator) { ... } // ❌ 오류
    }
    

    → 위 코드는 컴파일 에러다. 왜?? T가 무엇인지 모르기 때문이다..!

    public class CalculatorUtil {
        public static <T extends Number> double calculate(T a, T b, OperatorType operator) {
            // 생략
            double num1 = a.doubleValue();
            double num2 = b.doubleValue();
            // 생략
        }
    }
    

    <T extends Number> 를 저렇게 앞에 선언해야 calculate 메서드는 제네릭 메서드가 되고, 호출할 때 타입 추론이 가능해져서 명시적으로 타입을 줄 수 있다고 한다.

    <aside> 📌

    나는 1번으로 사용했다!!!

    왜?

    내부에서 a.doubleValue()처럼 Number 메서드만 사용할 거라 굳이 제네릭으로 받을 필요 없이 그냥 Number 타입으로 받았다. 어차피 다 더블이야~

    </aside>

  3. 내부 int에서 double로 변경

    <aside> 📌

    lv1과 lv2에서는 양의 정수만 사용했기 때문에 int를 사용했는데 lv3에서는 실수를 받아야 해서 double로 변경해 주고 음수를 거르는 if문 조건은 삭제해 주었다.

    </aside>

  4. double과 Double의 차이

    <aside> 📌

    double은 null값을 받을 수 없는 기본 자료형이고 Double은 레퍼 클래스이다. 제네릭/컬렉션에서는 double을 사용하지 못하고 Double만 사용 가능하다.

    </aside>

  5. 나눗셈에서 undefined를 설정했는데 double로 바꾸고 나서는 Infinity가 나오는 이유와 Infinity가 아니라 undefined가 나오게 설정하는 법

    <aside> 📌

    int로 했을 때는 undefined로 잘 내려왔는데 double로 바꾸고 나니 Infinity가 나온다. 알고 보니 double 타입에서는 0으로 나누는 게 InfinityNaN로 반환하고 에러로 떨어지지 않는 것이었다. 그럼 이 상태에서 undefined로 result를 보여주고 싶다면 어떻게 해야할까? 방법은 throw new ArithmeticException를 날려서 try-catch로 잡아주는 것이었다.

    </aside>

    case DIV -> {
        if (num2 == 0.0) {
            throw new ArithmeticException("0으로 나눌 수 없습니다.");
        }
        yield num1 / num2;
    }
    
    try {
        double calcResult = CalculatorUtil.calculate(num1, num2, operator);
        result = String.valueOf(calcResult);
    } catch (ArithmeticException e) {
        // System.out.println(e.getMessage()); 0으로 나눌 수 없습니다.
        result = "undefined";
    } catch (IllegalArgumentException e) {
        result = "잘못된 연산입니다.";
    }
    
  6. Enum에서 연산자 찾는 방법

    <aside> 📌

    Enum을 썼는데 내가 가져오는 +,-,*,/가 ADD인지 DIV인지 어떻게 알지?라는 의문이 생겼다. 향상된 for문을 돌려서 입력한 값이 OperatorType Enum 중에 어떤 거랑 매칭되는지 찾아야했다. 내가 +를 입력하면 ADD를 반환하는데 +와 ADD가 매핑되어 있기 때문!!

    </aside>

    public static OperatorType matchOperator(char operator) {
        for (OperatorType op : values()) {
            if (op.getOperator() == operator) {
                return op;
            }
        }
        throw new IllegalArgumentException("지원하지 않는 연산자입니다: " + operator);
    }