개요
CH 2 계산기과제를 진행하며 고민했던 부분들을 작성해보겠습니다 ✏️✏️✏️
📌 계산기 과제 개발 프로세스 가이드
### Lv 1. 클래스 없이 기본적인 연산을 수행할 수 있는 계산기 만들기
- **양의 정수(0 포함)를 입력받기**
- Scanner를 사용하여 양의 정수 2개(0 포함)를 전달 받을 수 있습니다.
- 양의 정수는 각각 하나씩 전달 받습니다.
- 양의 정수는 적합한 타입으로 선언한 변수에 저장합니다.
- **사칙연산 기호(➕,➖,✖️,➗)를 입력받기**
- Scanner를 사용하여 사칙연산 기호를 전달 받을 수 있습니다.
- 사칙연산 기호를 적합한 타입으로 선언한 변수에 저장합니다. (`charAt(0)`)
- **위에서 입력받은 양의 정수 2개와 사칙연산 기호를 사용하여 연산을 진행한 후 결과값을 출력하기**
- 키워드 : `if` `switch`
- 사칙연산 기호에 맞는 연산자를 사용하여 연산을 진행합니다.
- 입력받은 연산 기호를 구분하기 위해 제어문을 사용합니다. (예를 들면 if, switch)
- 연산 오류가 발생할 경우 해당 오류에 대한 내용을 정제하여 출력합니다.
- ex) “나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다.“
- **반복문을 사용하되, 반복의 종료를 알려주는 “exit” 문자열을 입력하기 전까지 무한으로 계산을 진행할 수 있도록 소스 코드를 수정하기**
- 키워드 : 무한으로 반복, 수정하기 (처음부터 무한 반복하는 것이 아니라, 위 스텝별로 진행하며 수정)
- 반복문을 사용합니다. (예를 들어, for, while…)
### Lv 2. 클래스를 적용해 기본적인 연산을 수행할 수 있는 계산기 만들기
- 사칙연산을 수행 후, 결과값 반환 메서드 구현 & 연산 결과를 저장하는 컬렉션 타입 필드를 가진 Calculator 클래스를 생성
- 사칙연산을 수행한 후, 결과값을 반환하는 메서드 구현
- 연산 결과를 저장하는 컬렉션 타입 필드를 가진 Calculator 클래스를 생성
- 1) 양의 정수 2개(0 포함)와 연산 기호를 매개변수로 받아 사칙연산(➕,➖,✖️,➗) 기능을 수행한 후 2) 결과 값을 반환하는 메서드와 연산 결과를 저장하는 컬렉션 타입 필드를 가진 Calculator 클래스를 생성합니다.
- **Lv 1에서 구현한 App 클래스의 main 메서드에 Calculator 클래스가 활용될 수 있도록 수정**
- 연산 수행 역할은 Calculator 클래스가 담당
- 연산 결과는 Calculator 클래스의 연산 결과를 저장하는 필드에 저장
- 소스 코드 수정 후에도 수정 전의 기능들이 반드시 똑같이 동작해야합니다.
- **App 클래스의 main 메서드에서 Calculator 클래스의 연산 결과를 저장하고 있는 컬렉션 필드에 직접 접근하지 못하도록 수정 (캡슐화)**
- 간접 접근을 통해 필드에 접근하여 **가져올** 수 있도록 구현합니다. (Getter 메서드)
- 간접 접근을 통해 필드에 접근하여 **수정할** 수 있도록 구현합니다. (Setter 메서드)
- 위 요구사항을 모두 구현 했다면 App 클래스의 main 메서드에서 위에서 구현한 메서드를 활용 해봅니다.
- Calculator 클래스에 저장된 연산 결과들 중 가장 먼저 저장된 데이터를 삭제하는 기능을 가진 메서드를 구현한 후 App 클래스의 main 메서드에 삭제 메서드가 활용될 수 있도록 수정
- 키워드 : `컬렉션`
- 컬렉션에서 ‘값을 넣고 제거하는 방법을 이해한다.’가 중요합니다!
### Lv 3. Enum, 제네릭, 람다 & 스트림을 이해한 계산기 만들기
- **현재 사칙연산 계산기는 (➕,➖,✖️,➗) 이렇게 총 4가지 연산 타입으로 구성되어 있습니다.**
- Enum 타입을 활용하여 연산자 타입에 대한 정보를 관리하고 이를 사칙연산 계산기 ArithmeticCalculator 클래스에 활용 해봅니다.
- **실수, 즉 double 타입의 값을 전달 받아도 연산이 수행하도록 만들기**
- 키워드 : `제네릭`
- 단순히, 기존의 Int 타입을 double 타입으로 바꾸는 게 아닌 점에 주의하세요!
- 지금까지는 ArithmeticCalculator, 즉 사칙연산 계산기는 양의 정수(0 포함)를 매개변수로 전달받아 연산을 수행
- 피연산자를 여러 타입으로 받을 수 있도록 기능을 확장
- ArithmeticCalculator 클래스의 연산 메서드(`calculate`)
- 위 요구사항을 만족할 수 있도록 ArithmeticCalculator 클래스를 수정합니다. (제네릭)
- 추가적으로 수정이 필요한 다른 클래스나 메서드가 있다면 같이 수정 해주세요.
- **저장된 연산 결과들 중 Scanner로 입력받은 값보다 큰 결과값 들을 출력**
- ArithmeticCalculator 클래스에 위 요구사항을 만족하는 조회 메서드를 구현합니다.
- 단, 해당 메서드를 구현할 때 Lambda & Stream을 활용하여 구현합니다.
- Java 강의에서 람다 & 스트림을 학습 및 복습 하시고 적용 해보세요!
📌 계산기 과제 TIL 모음
2025.01.02 - [TIL (Today I Learned)] - [TIL] Java-객체지향 프로그래밍, 계산기 만들기 (25-01-02)
[TIL] Java-객체지향 프로그래밍, 계산기 만들기 (25-01-02)
🤖 진행 사항 🤖 Java 문법 종합반 3주차Java 계산기 과제Lv 1. 클래스 없이 기본적인 연산을 수행할 수 있는 계산기 만들기Lv 2. 클래스를 적용해 기본적인 연산을 수행할 수 있는 계산기
mannakingdom.tistory.com
[TIL] Java 계산기 만들기, 계산기 예외처리 과제 / Java Error와 Exception, Generic (25-01-03)
🤖 25-01-03 진행 사항🤖3주차 과제 : 계산기 만들기 - 추상화 클래스, 클래스 포함 관계 적용Java 문법 종합반 - 4주차Error / ExceptionGenetic4주차 과제 : 계산기 예외처리계산기 만들기 - 추상화 클래스
mannakingdom.tistory.com
2025.01.06 - [TIL (Today I Learned)] - [TIL] Java Enum과 Generic 활용하여 계산기 만들기 (25-01-06)
[TIL] Java Enum과 Generic 활용하여 계산기 만들기 (25-01-06)
🤖 25-01-06 진행 사항 🤖ch2. 계산기 과제 Lv 3 : Enum, 제네릭, 람다&스트림을 이해한 계산기 만들기EnumGeneric예외처리EnumEnum 타입을 활용하여 연산자 타입에 대한 정보를 관리하고 이를 사칙연산 계
mannakingdom.tistory.com
고민했던 부분
1. 나누기 연산 예외 처리
나누기 연산에서 분모가 0일 경우 반환되는 예외 처리를 해야했다.
lv 1을 진행할 때는 `switch`문에서, lv 2에서는 `if`문으로 나누기 연산이면서 입력된 두번째 정수가 0이면 안내문이 출력되도록 했다.
- App.java
if ((operator == '/' || operator == '➗') && num2 == 0) {
System.out.println("나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다.");
}
lv 3를 진행하면서 해당 예외 처리에 대한 고뇌가 시작됐다.
lv 3에서 생성한 `OperatorType` Enum 클래스에 나누기 연산이 맞는지 확인하는 메서드를 추가하여 다음과 같이 확인했다.
- OperatorType.java
// 나눗셈 연산인지 판별
public static boolean isDivisionOperator(char input) {
if (OperatorType.DIVIDE.operationSymbol == input
|| OperatorType.DIVIDE.operationEmoji == input) {
return true;
}
return false;
}
- App.java
if (OperatorType.isDivisionOperator(operator) && numbers[1].equals(0)) {
System.out.println("나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다.");
}
조건은 맞지만 더 간결하고 보기 좋게 예외 처리를 하고싶어서, 검색을 해보다가 산술 작업에서 오류가 발생한 경우 throw되는 예외인 ArithmeticException을 알게됐다.

그런데 위와 같이 적용을 하니 오류가 throw되지 않고 결과값으로 NaN(Not-a-Number)값과 Infinity가 출력됐다.
- 0 / 0 ➡️ NaN
- 0이 아닌 숫자 / 0 ➡️ Infinify
lv 3을 진행하면서 전달받는 매개변수들에 제네릭을 적용하게 됐고, 사용자에게 숫자를 입력받을 때도 `Number`형 배열에 `double`값으로 저장을 하게되어서 `ArithmeticException`이 발생하지 않는 것이었다.
`ArithmeticException`은 정수 연산에서 0으로 나누기와 같은 문제에서 발생하기 때문에 `ArithmeticException`를 이용한 예외처리는 포기하게 됐다.
마지막으로 채택하게 된 예외처리는 IllegalArgumentException이다.
✏️ IllegalArgumentException
호출자가 인수로 부적절한 값을 넘길 때 던지는 예외다.
자바 API에서 제공하는 표준 예외를 사용해서 가독성을 높이고 간단하게 표현했다.
- App.java
// 사칙연산 기호(➕,➖,✖️,➗) 입력 받기
while (true) {
System.out.print("사칙연산 기호를 입력하세요: ");
try {
char operator = sc.next().trim().charAt(0);
// 입력받은 사칙연산 기호가 올바른지 판별
if (!OperatorType.isValidOperator(operator)) {
throw new IllegalArgumentException("사칙연산 기호가 적절하지 않습니다.");
}
// 나눗셈 연산이 유효한지 판별
if (!OperatorType.isValidDivision(operator, numbers[1])) {
throw new IllegalArgumentException("나눗셈의 분모(두 번째 숫자)가 0일 수 없습니다.");
}
calculator.setOperator(operator);
break; // 유효한 연산자 입력 시 반복 종료
} catch (Exception e) {
System.out.println(e.getMessage() + " 사칙연산 기호를 다시 입력하세요.");
System.out.println("사용 가능한 사칙연산 기호: +, -, *, /, ➕, ➖, ✖, ➗");
System.out.println("--------------------------------------------------");
}
}
`OperatorType` Enum에는 나눗셈 연산이 유효한지 판별해주는 메서드를 다시 만들었다.
매개변수로 연산자와 입력받은 두 번째 숫자를 전달했다.
- OperatorType.java

2. 연산 메서드 캡슐화
`ArithmeticCalculator`클래스의 `calculate`메서드에서 `switch`문의 `case '➕', '+'`와 같은 방식으로 직접적으로 연산자를 확인하는 것이 비효율적으로 보여 수정하고 싶었다.

이미 `OperatorType`에 연산자들('➕', '+' 등)이 상수마다 필드로 저장됐기 때문에, 이를 활용하는 방법을 찾아보았다.

`ArithmeticCalculator`클래스의 `operator`필드에 저장된 값이 `OperatorType` Enum에서 어떤 상수인지 확인하는 메서드가 필요했다.
다음과 같이 `selectOperator`메서드를 생성했다.
- OperatorType.java
// 어떤 연산자를 사용하는지 반환해주는 메서드
public static OperatorType selectOperator(char input) {
for (OperatorType op : OperatorType.values()) {
if (op.operationSymbol == input || op.operationEmoji == input) {
return op;
}
}
throw new IllegalArgumentException("사칙연산 기호가 적절하지 않습니다.");
}
enum의 기본 제공 메서드 `values()`를 사용해서 모든 상수에 대해 input값과 같은게 있는지 비교하고, 같은게 없다면 `IllegalArgumentException`을 throw하도록 했다.
생성한 `selectOperator`메서드를 활용하여, `ArithmeticCalculator`클래스의 `calculate`메서드를 수정하면 다음과 같다.
- ArithmeticCalculator.java
// 사칙연산을 수행한 후, 결과값을 반환하는 메서드 구현
// 매개변수 타입은 제네릭, 반환은 항상 double
public double calculate(T num1, T num2) {
OperatorType operatorType = OperatorType.selectOperator(this.operator);
return operatorType.operate(num1, num2);
}
`switch`문을 사용할 때 보다 훨씬 가독성이 좋아지고 유지보수성이 좋아진 것 같다❗ ❗ ❗
아쉬운 점
1. Try-Catch
`try-catch`의 활용 범위(어디까지 묶는지)와 어떤 부분에 예외 처리를 넣어야하는지 명확하지 않았다.
while문 전체에 try-catch로 묶는 것이 나을지, 세부적으로 처리해야 할지 판단이 어려웠다.
앞으로 더 큰 프로젝트를 하고 구현하는 범위도 넓어질텐데, 어떤 기준으로 판단해야할지 잘 모르겠다.........🥲
예외 처리에 대한 공부가 더 필요할 것 같다.
2. <T extends Number> 활용의 모호함
제네릭 타입으로 구현한 `ArithmeticCalculator`에서 <T extends Number>를 사용했지만, 실제 연산은 모두 `double`형으로 처리하고 반환한다.
`double`형으로 처리하게 만든 이유는, 일반적인 산술 결과가 실수라고 판단했기 때문이다.
예를 들어 3 / 2 의 경우 입력값이 정수여도 결과값으로 1.5 라는 실수를 반환해줘야 한다고 생각했다.
- OperatorType.java
// 매개변수는 제네릭 타입(Number 또는 그 하위 클래스들)으로 받고,
// 반환 타입을 항상 double 타입으로 고정: 일반적인 산술 결과가 실수이기 때문에
public abstract <T extends Number> double operate(T num1, T num2);
그래서 사용자에서 숫자를 입력받을 때도 저장할 배열은 `Number`형이지만, 저장하는 값은 `Double`로 파싱해서 저장했다.
- App.java
// 연산할 숫자 2개 입력 받기
// 입력 받은것이 숫자가 맞는지 확인
String userInput;
Number[] numbers = new Number[2];
for (int i = 0; i < 2; i++) {
System.out.print((i + 1) + " 번째 숫자를 입력하세요: ");
userInput = sc.next().trim();
try {
numbers[i] = Double.parseDouble(userInput);
} catch (NumberFormatException e) {
System.out.println(userInput + "은 숫자가 아닙니다. 숫자를 다시 입력하세요.");
System.out.println("--------------------------------------------------");
--i;
}
}
이런식으로 구현하다보니 결국 모든 값을 그냥 `double`로 다루고 있었다...🥲🥲🥲🥲🥲
제네릭 활용을 제한적으로 하게되어서 아쉽고 올바른? 적절한? 활용법이 궁금하다.
그리고 `long`타입의 숫자를 입력했을 때, 연산을 `double`형으로 하기 때문에 부동소수점 연산/출력이 되는 점을 발견했다.

부동소수점 연산 방식으로 출력을 하게되어 입력과 출력의 일관성이 없어진 점이 아쉽다.
마무리
계산기 과제를 하면서 평소엔 안 해봤던 예외 처리나 리팩토링에 대해서 고민해 볼 수 있어서 좋았다.
특히 Lv 3를 진행하면서 이론적으로 몰랐던 부분을 공부하고 직접 적용해 보면서 더 확실하게 학습이 된 것 같아서 뿌듯하다!
https://github.com/mannaKim/java-calculator
GitHub - mannaKim/java-calculator: 계산기 구현 Java 프로그래밍 과제
계산기 구현 Java 프로그래밍 과제. Contribute to mannaKim/java-calculator development by creating an account on GitHub.
github.com
'TIL (Today I Learned)' 카테고리의 다른 글
| [Java] 예외 처리 방법: 기본 구조, throw/throws, 사용자 정의 예외 (1) | 2025.01.10 |
|---|---|
| [Java] 스택 자료구조: Stack과 Deque 비교 (0) | 2025.01.10 |
| [Java] 람다(Lambda)와 스트림(Stream) API 개념 및 예제 (1) | 2025.01.07 |
| [TIL] Java Enum과 Generic 활용하여 계산기 만들기 (25-01-06) (1) | 2025.01.06 |
| [Java] Generic의 원리와 활용법 (0) | 2025.01.06 |