스파르타 12일차

임선구·5일 전

스파르타

목록 보기
12/14

Java 계산기 과제에서 제네릭과 Number 이해하기

정수 전용 계산기에서 실수까지 처리하도록 확장해보기

계산기 과제를 진행하면서 처음에는 int만 사용하는 계산기를 만들었다.
즉, 정수끼리만 계산할 수 있는 구조였다.

예를 들어 이런 입력은 가능했다.

  • 1 + 2
  • 10 - 3
  • 5 * 4

하지만 이런 입력은 처리할 수 없었다.

  • 1.5 + 2.3
  • 5 / 2 = 2.5

즉, 기존 계산기는 정수 전용 계산기였다.

그런데 과제를 진행하면서
실수도 받을 수 있게 확장하라,
그리고 제네릭을 활용하라는 요구사항이 나왔다.

처음에는
“그냥 intdouble로 바꾸면 되는 거 아닌가?”
라고 생각했지만, 과제의 핵심은 단순히 실수 계산이 아니라
여러 숫자 타입을 더 유연하게 받을 수 있는 구조를 이해하는 것이었다.

이번 글에서는 계산기 과제를 통해 배운
제네릭과 Number의 역할,
그리고 doubleValue()를 사용했는지를 정리해보려고 한다.


1. 처음 계산기는 왜 한계가 있었을까?

기존 계산 메서드는 이런 느낌이었다.

public int calculate(int a, int b, OperatorType operatorType) {
    int result = 0;

    if (operatorType == OperatorType.ADD) {
        result = a + b;
    } else if (operatorType == OperatorType.SUBTRACT) {
        result = a - b;
    } else if (operatorType == OperatorType.MULTIPLY) {
        result = a * b;
    } else if (operatorType == OperatorType.DIVIDE) {
        result = a / b;
    }

    return result;
}

이 구조의 문제는 명확했다.

  • a, bint라서 정수만 받을 수 있다
  • 나눗셈 결과도 정수로 잘릴 수 있다
  • 실수 입력인 1.2, 2.5를 받을 수 없다

예를 들어:

5 / 2

를 계산하면 수학적으로는 2.5여야 하지만,
정수 계산에서는 2가 나와버린다.

즉, 정수 타입에 너무 묶여 있는 구조였다.


2. 그럼 그냥 double로 바꾸면 되지 않을까?

처음에는 이런 생각을 했다.

public double calculate(double a, double b, OperatorType operatorType)

이렇게 바꾸면 정수도 받을 수 있고, 실수도 받을 수 있다.

실제로 이 방식도 동작은 한다.
하지만 과제에서는 여기서 한 걸음 더 나아가
제네릭을 사용해서 여러 숫자 타입을 받을 수 있는 구조를 이해하는 것이 중요했다.

즉 핵심은

단순히 double 계산기를 만드는 것이 아니라,
숫자 타입에 유연한 계산기 구조를 만드는 것

이었다.


3. 제네릭(Generic)이란?

제네릭은 쉽게 말하면

타입을 나중에 정하는 문법

이다.

예를 들어 아래 코드를 보자.

public class Box<T> {
    private T item;
}

여기서 T는 아직 정해지지 않은 타입이다.

나중에 사용할 때

  • Box<String>
  • Box<Integer>

처럼 실제 타입을 넣어서 사용할 수 있다.

T는 일종의 임시 타입 이름이다.


4. 계산기에서 왜 제네릭이 필요했을까?

계산기의 입력값은 이제

  • 정수일 수도 있고
  • 실수일 수도 있다

즉 어떤 때는 Integer, 어떤 때는 Double이 들어올 수 있다.

그런데 이런 서로 다른 숫자 타입을
하나의 계산기 메서드가 받을 수 있으면 좋겠다고 생각했다.

그래서 계산기 클래스를 이렇게 바꿨다.

public class ArithmeticCalculator<T extends Number> {
}

5. T extends Number는 무슨 뜻일까?

이 문장은 처음 보면 조금 어렵게 느껴진다.

public class ArithmeticCalculator<T extends Number>

하지만 뜻은 생각보다 단순하다.

  • T = 나중에 정해질 타입
  • extends Number = 그런데 아무 타입이나 안 되고, 숫자 타입만 허용

즉 이 계산기는

  • Integer
  • Double
  • Long
  • Float

같은 숫자 타입만 받을 수 있다는 의미다.

반대로 이런 건 받을 수 없다.

  • String
  • Student

즉 이 코드는 한 줄로 이렇게 해석할 수 있다.

숫자 타입만 받을 수 있는 계산기 클래스를 만들겠다


6. Number는 무엇인가?

Number는 자바에서 숫자 타입들의 부모 클래스 같은 존재다.

대표적으로 이런 클래스들이 Number 계열이다.

  • Integer
  • Double
  • Long
  • Float

Number
“숫자 타입들의 공통 부모”
라고 이해하면 편하다.

그래서 T extends Number라고 쓰면,
정수도 가능하고 실수도 가능한 구조를 만들 수 있다.


7. 실제 계산기 코드는 어떻게 바뀌었을까?

기존 Calculator 클래스를
ArithmeticCalculator로 바꾸고,
calculate 메서드를 이렇게 수정했다.

import java.util.ArrayList;
import java.util.List;

public class ArithmeticCalculator<T extends Number> {

    private List<Double> results = new ArrayList<>();

    public double calculate(T num1, T num2, OperatorType operatorType) {

        double num1Double = num1.doubleValue();
        double num2Double = num2.doubleValue();

        double result = 0;

        if (operatorType == OperatorType.ADD) {
            result = num1Double + num2Double;
        } else if (operatorType == OperatorType.SUBTRACT) {
            result = num1Double - num2Double;
        } else if (operatorType == OperatorType.MULTIPLY) {
            result = num1Double * num2Double;
        } else if (operatorType == OperatorType.DIVIDE) {
            result = num1Double / num2Double;
        }

        results.add(result);
        return result;
    }
}

8. 여기서 왜 doubleValue()를 썼을까?

이 부분이 가장 중요했다.

double num1Double = num1.doubleValue();
double num2Double = num2.doubleValue();

num1, num2T extends Number이기 때문에
실제로는 Integer일 수도 있고 Double일 수도 있다.

예를 들면:

  • num1 = Integer 3
  • num2 = Double 2.5

이런 식이다.

그런데 계산을 하려면 타입을 하나로 통일하는 게 편하다.
그래서 계산 전에 모두 double로 바꾼 것이다.

Number 계열은 공통적으로 doubleValue() 메서드를 가지고 있기 때문에,
어떤 숫자 타입이 들어와도 double로 바꿀 수 있다.

예를 들어:

Integer a = 3;
Double b = 2.5;

System.out.println(a.doubleValue()); // 3.0
System.out.println(b.doubleValue()); // 2.5

doubleValue()

어떤 숫자 타입이 오든 계산하기 편한 double로 통일하는 역할

을 한다고 이해하면 된다.


9. 왜 결과는 List<T>가 아니라 List<Double>로 저장했을까?

처음에는 나도 이런 생각을 했다.

입력 타입이 T니까 결과도 List<T>로 저장하면 되는 거 아닌가?

그런데 곰곰이 생각해보면 문제가 있다.

예를 들어 계산기가 Integer를 받는다고 해도
나눗셈 결과는 실수가 될 수 있다.

5 / 2 = 2.5

즉 입력 타입이 Integer라고 해서
결과도 항상 Integer가 되는 건 아니다.

그래서 결과 저장은 아예

private List<Double> results = new ArrayList<>();

이렇게 Double 타입으로 통일했다.

이렇게 하면

  • 정수 결과도 저장 가능 (3 → 3.0)
  • 실수 결과도 저장 가능 (2.5 → 2.5)

즉 결과를 더 유연하게 저장할 수 있다.


10. App에서는 어떻게 입력을 처리했을까?

입력값은 더 이상 nextInt()로 바로 받을 수 없었다.
왜냐하면 1.2 같은 실수 입력도 받아야 하기 때문이다.

그래서 입력은 먼저 문자열로 받고,
그 문자열이 정수인지 실수인지 판단해서 숫자로 바꾸는 방식을 사용했다.

System.out.print("첫 번째 숫자를 입력하세요: ");
String input1 = scanner.next();

System.out.print("두 번째 숫자를 입력하세요: ");
String input2 = scanner.next();

Number num1 = parseNumber(input1);
Number num2 = parseNumber(input2);

그리고 parseNumber() 메서드는 이렇게 만들었다.

private static Number parseNumber(String number) {
    if (number.contains(".")) {
        return Double.parseDouble(number);
    } else {
        return Integer.parseInt(number);
    }
}

  • "3" 입력 → Integer
  • "1.5" 입력 → Double

로 처리하도록 만든 것이다.

이렇게 하면 App 입장에서는
정수와 실수를 구분해서 적절한 숫자 타입으로 바꾸고,
계산기에는 Number로 넘겨줄 수 있게 된다.


11. 정리하면 이번 과제에서 배운 핵심은?

이번 제네릭 과제를 하면서 정리된 핵심은 이렇다.

1) 제네릭은 타입을 유연하게 만든다

ArithmeticCalculator<T extends Number>

이렇게 하면 정수와 실수 같은 여러 숫자 타입을 받을 수 있다.

2) Number는 숫자 타입들의 공통 부모다

Integer, Double 같은 숫자 타입을 하나의 공통 타입으로 다룰 수 있게 해준다.

3) doubleValue()는 계산 전에 타입을 통일하는 역할을 한다

정수든 실수든 상관없이 double로 바꿔 계산하면 훨씬 편하다.

4) 결과는 Double로 저장하는 것이 자연스럽다

입력은 정수일 수도 있지만, 결과는 실수가 될 수 있기 때문이다.


12. 느낀 점

처음에는 T extends Number가 굉장히 추상적으로 느껴졌다.
하지만 계산기처럼 “정수도 받고 실수도 받고 싶다”는 상황에 적용해보니,
왜 이런 문법이 필요한지 조금 이해가 됐다.

이번 과제를 통해 느낀 건,

제네릭은 단순히 어려운 문법이 아니라,
타입을 더 유연하고 재사용 가능하게 만드는 도구라는 점이었다.

그리고 NumberdoubleValue()를 함께 사용하면
정수와 실수를 하나의 계산 흐름 안에서 자연스럽게 다룰 수 있다는 것도 알게 되었다.


마무리

이번 계산기 과제에서 제네릭과 Number를 적용해보면서
기존의 정수 전용 계산기를
정수와 실수 모두 처리 가능한 계산기로 확장할 수 있었다.

한 줄로 정리하면:

T extends Number는 숫자 타입만 받게 제한하는 것이고,
doubleValue()는 그 숫자들을 계산하기 쉬운 형태로 통일하는 역할을 한다.

처음에는 조금 어렵게 느껴졌지만,
직접 계산기에 붙여보면서 제네릭이 “왜 필요한지”를 조금은 이해할 수 있었던 시간이었다.


profile
끝까지 가면 내가 다 이겨

0개의 댓글