2025. 1. 10. 20:05ㆍ프로그래밍 언어/Java
개요
GitHub - f-api/calculator
Contribute to f-api/calculator development by creating an account on GitHub.
github.com
해당 블로그 글은 본 저작자의 허가를 받았습니다.
과제로 주어졌던 Lv3 을 넘어서 Lv4(가칭) 을 분석을 해보고자 한다.
기존 Lv3 와 Lv4는 어떤 차이가 있는지 확인해보자.
Lv3 와 Lv4는 어떤 차이가 존재하는가?
- 숫자의 제한이 없다.
- 나누기 연산에 소수점 연산을 반올림을 하였다.
이 정도의 미묘한 차이점이 있다.
하지만 다른 것보다 우리는 얼마나 코드에 가독성이 좋은지도 같이 확인하면 좋을 듯하다.
*추가내용
Double에서 다른 타입으로 형변환을 하게 되면 데이터 손실 가능성이 있음.
그렇기 때문에 타입변환시 좀더 안정성이 높은 BigDecimal을 선택
해당 코드는 GoF의 전략패턴이 적용이 된 코드임.
추후 CS 포스팅을 할 예정
Lv4 구성도
구상도 설명
App에서 유저에게 정보를 받아오며 시작이 된다.
연산자를 받으면 Enum 클래스에서 어떤 Operation을 사용할지 값이 오게된다.
이제 입력받은 값(BigDecimal) 과 Operation을 ArithmeticCalculator 에게 매개변수로 보내게 된다.
이제 NumberUtils로 어떤 타입인지 확인을 받고 게산을 하게 된다.
자세한 설명은 밑에서 하도록 하겠다.
코드 리뷰
1. App.java
import java.math.BigDecimal;
import java.util.List;
import java.util.Scanner;
public class App {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
ArithmeticCalculator<BigDecimal> calculator = new ArithmeticCalculator<>(BigDecimal.class);
// 숫자, 연산자 입력 받기
while (true) {
System.out.print("첫 번째 숫자를 입력하세요: ");
BigDecimal firstNumber = sc.nextBigDecimal();
System.out.print("두 번째 숫자를 입력하세요: ");
BigDecimal secondNumber = sc.nextBigDecimal();
System.out.print("사칙연산 기호를 입력하세요 (+, -, *, /): ");
String symbol = sc.next();
// Enum 클래스에서 어떤 Operation을 받을 것인지
Operator operator = Operator.fromSymbol(symbol);
System.out.println("결과: " + calculator.calculate(firstNumber, secondNumber, operator));
System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
if ("exit".equals(sc.next())) {
break;
}
System.out.println("저장된 연산 결과 중 특정 값보다 큰 결과를 조회하시겠습니까? (yes 입력 시 조회)");
if ("yes".equals(sc.next())) {
System.out.print("기준 값을 입력하세요: ");
BigDecimal next = sc.nextBigDecimal();
List<BigDecimal> filteredResults = calculator.getResultsGreaterThan(next);
System.out.println("기준 값보다 큰 결과들: " + filteredResults);
}
}
}
}
BigDecimal은 무엇인가
간단하게 설명을 하면 크기에 제한이 없는 소수점까지 저장을 할 수 있는 변수형 타입이다.
내부에서는 char 배열로 값을 저장을 하기 때문에 제한이 없다.
* 알아두면 좋은 것
- BigDecimal, BigInteger 은 길이의 제한이 없다.
- 내부에서는 char배열로 값을 저장을 한다. ( 그래서 String으로 초기화가 가능하다. )
- BigDecimal, BigInteger는 Number을 상속받는 클래스이다.
- 내부 위치는 java.lang 패키지에 속하는 것이 아닌 java.math 패키지에 속한다.
참고자료
BigDecimal.class도 참조하였다.
오라클 자바 8 BigDecimal 참고
BigDecimal (Java Platform SE 8 )
Returns a string representation of this BigDecimal, using engineering notation if an exponent is needed. Returns a string that represents the BigDecimal as described in the toString() method, except that if exponential notation is used, the power of ten is
docs.oracle.com
App의 역할
App은 유저에게 정보를 입력을 받고 컨트롤을 해주는 역할을 한다.
순서
- firstNumber, secondNumber, Operator을 입력을 받는다.
- Operator 클래스를 호출하여 매개변수로 어떤 Operation을 받을지 받아온다.
- 계산기에게 값을 보내준다.
- 저장된 연산 결과 중 값 조회
사실 여기까지는 많이 익숙한 코드일 것이다.
2. Operator.java
package com.example.calculator4;
public enum Operator {
ADD("+", new AddOperation()),
SUBTRACT("-", new SubtractOperation()),
MULTIPLY("*", new MultiplyOperation()),
DIVIDE("/", new DivideOperation());
private final String symbol;
// Operation interface
private final Operation operation;
// 생성자 Enum 생성자는 private 라서 외부 접근이 안된다.
Operator(String symbol, Operation operation) {
this.symbol = symbol;
this.operation = operation;
}
// +, -, *, / 을 가져온다.
public String getSymbol() {
return symbol;
}
// Operator 에서 계산을 넘길수 있도록 선언
public <T extends Number> T apply(T first, T second, Class<T> type) {
// Operation 객체
return operation.apply(first, second, type);
}
// static으로 선언을 하면 생성자 없이 클래스만 불러서 외부에서 사용이 가능하다.
public static Operator fromSymbol(String symbol) {
for (Operator operator : values()) {
if (operator.getSymbol().equals(symbol)) {
return operator;
}
}
throw new IllegalArgumentException("유효하지 않은 연산 기호입니다. (+, -, *, / 중 하나여야 합니다)");
}
}
Enum 클래스에서는 메소드만 설명을 하겠다.
- getSymbol 은 +, -, /, * 와 같은 연산자를 돌려준다.
- T apply 는 enum 클래스 두번째 속성이 Operation 타입(클래스) 을 가지고 있어서
매개변수만 넘겨주면 Operation 객체에서 계산을 하고 리턴을 해준다. - public static Operator fromSymbol 은
static으로 선언이 되어있기 때문에 외부에서 Operator.fromSymbol을 하면
operation 값(클래스 객체) 을 찾아서 return 한다.
참고 자료
[Java] enum(열거형)
enum(열거형)이란, 서로 관련된 상수를 편하게 선언하기 위한 것으로 상수를 여러개 정의해야 할 때 사용한다. 즉, 서로 연관된 상수들의 집합이다. 수많은 언어는 사용자들이 새로운 열거형을 정
codingwell.tistory.com
Java Enum 활용기 | 우아한형제들 기술블로그
안녕하세요? 우아한 형제들에서 결제/정산 시스템을 개발하고 있는 이동욱입니다. 이번 사내 블로그 포스팅 주제로 저는 Java Enum 활용 경험을 선택하였습니다. 이전에 개인 블로그에 Enum에 관해
techblog.woowahan.com
3. ArithmeticCalculator.java
import static com.example.calculator4.NumberUtils.convertToBigDecimal;
public class ArithmeticCalculator<T extends Number> {
private final List<T> resultList = new ArrayList<>();
private final Class<T> type;
// ArithmeticCalculator<BigDecimal> calculator = new ArithmeticCalculator<>(BigDecimal.class);
// 메인에 있는 new ArithmeticCalculator<>(BigDecimal.class); 에서
// 클래스를 넘겨주는 것을 확인할 수 있다.
public ArithmeticCalculator(Class<T> type) {
this.type = type;
}
// 전달받은 Operator 에는 Operation 객체가 있음.
public T calculate(T firstNumber, T secondNumber, Operator operator) {
T result = operator.apply(firstNumber, secondNumber, type);
addResult(result);
return result;
}
// App 마지막에 비교 대상 입력받고 출력하는 것.
public List<T> getResultsGreaterThan(T value) {
return resultList.stream()
// r ( 리스트 중 하나 ) 을 BigDecimal 타입을 변환하고
// compareTo ( 크면 1, 같으면 0, 작으면 -1 리턴 )
// 0보다 크면 내용에 저장
.filter(r -> convertToBigDecimal(r).compareTo(convertToBigDecimal(value)) > 0)
// List로 변환
.collect(Collectors.toList());
}
// List에 추가
public void addResult(T result) {
resultList.add(result);
}
}
ArithmeticCalculator에서는 생성자로 타입을 지정을 해주는 모습을 확인 할 수 있다.
App main 메소드에서 new ArithmeticCalculator<>(BigDecimal.class); 로 BigDecimal.class을 보내주는 것을 확인 가능
calculate 메소드는 매개변수로 전달받은 Operator에는 Operation 객체가 있어서
Operator에서 계산이 가능하다. ( 메소드로 선언이 되어있다. )
이제 계산에 들어가니 Operation Interface에 속하는 클래스 하나만 확인을 해보겠다.
4. DivideOperation.java
import java.math.BigDecimal;
import java.math.RoundingMode;
import static com.example.calculator4.NumberUtils.castToType;
import static com.example.calculator4.NumberUtils.convertToBigDecimal;
public class DivideOperation implements Operation {
@Override
public <T extends Number> T apply(T a, T b, Class<T> type) {
// NumberUtils 에서 가져와서 사용
BigDecimal divisor = convertToBigDecimal(b);
if (BigDecimal.ZERO.compareTo(divisor) == 0) {
throw new IllegalArgumentException("나눗셈 연산에서 분모(두 번째 숫자)가 0일 수 없습니다.");
}
// RoundingMode.HALF_UP <= 0.5 기준 반올림
BigDecimal div = convertToBigDecimal(a).divide(divisor, RoundingMode.HALF_UP);
return castToType(div, type);
}
}
여기서 BigDecimal 클래스에 대해서 약간의 설명을 해야한다.
BigDecimal 같은 경우는 3개의 숫자에 대해서 final 로 기본으로 값이 저장되어있다.
각각 ZERO, ONE, TEN 을 가지고 있다.
BigDecimal 에서 기본적으로 연산를 지원을 한다. add, subtract, divide, multiply, remainder(%) 등 을 가지고 있다.
divide 연산뒤에 나눌 숫자와 RoundingMode.HALF_UP 이 있는데, 해당 모드는 반올림을 한다는 뜻이다.
왜 반올림을 쓰셨는지는 블로그를 남겨두도록 하겠다.
결론적으로 반올림은 안정성을 위해 사용하신것이라 보면 되겠다.
*추가내용
BigDecimal 은 divide을 그냥 실행을 하는 경우 밑에 오류가 나오게 된다.
하지만 없는 경우가 정상적인 출력이 되므로 사용할지는 개인이 선택을 하면 될 듯하다.
참고 자료
자바에서 실수형 연산 올바르게 하기.
자바에서는 실수 연산의 오차를 줄이기 위해 BigDecimal 클래스를 사용해야 한다.특히, 금액 계산에서 중요한 문제. 샘플코드> package sample.common; import java.math.BigDecimal;import java.math.MathContext;import java.
leeyongjin.tistory.com
5. NumberUtils
import java.math.BigDecimal;
public class NumberUtils {
// 입력받은 클래스에 맞게 변환
public static <T extends Number> T castToType(BigDecimal bigDecimal, Class<T> type) {
return switch (type.getSimpleName()) {
case "BigDecimal" -> type.cast(bigDecimal);
case "Integer" -> type.cast(bigDecimal.intValue());
case "Double" -> type.cast(bigDecimal.doubleValue());
case "Long" -> type.cast(bigDecimal.longValue());
default -> throw new IllegalArgumentException("지원하지 않는 타입입니다: " + type.getSimpleName());
};
}
// 숫자의 최상위 클래스인 Number을 사용해서 어떤 숫자가 오든 받을 수 있게 만든모습
public static BigDecimal convertToBigDecimal(Number number) {
return new BigDecimal(number.toString());
}
}
간단하게 설명을 해보자.
일단 밑에 있는 메소드인 convertToBigDecimal 을 먼저 살펴보겠다.
Number는 숫자의 최상위 클래스여서 어떤 숫자든 넣을 수 있다.
그 숫자를 BigDecimal로 변환을 해서 계산을 진행한다.
그 다음에 계산이 끝난 다음 castToType을 호출해서 BigDecimal 타입을 App 에 선언된 클래스에 맞게 변환을 하여 리턴을 해준다.
현재 App은 BigDecimal 타입이기 때문에 첫번째 것만 동작한다.
App에 있는 모든 BigDecimal 타입을 다른 타입으로 변환을 해준다면 나머지 케이스도 돌아갈 수 있게 된다.
느낀점
코드를 보면 Lv3 에서 도전과제로 내주었던 Enum 클래스와 제네릭만 뺀다면 어려운 코드가 없다.
처음 보는 사실 BigDecimal 클래스만 빼고, 제네릭도 계산을 어짜피 Double로 한다면
강의 3주차 까지 봤어도 만들 수 있다고 생각한다.
코드가 잘 읽히고, 변수를 봐도 한번에 이해가 되는 모습을 보면 이게 클린코드인가 생각이 든다.
깔끔하게 짜보도록 노력을 해봐야겠다.
'프로그래밍 언어 > Java' 카테고리의 다른 글
[Java] 추상 클래스에 대해서.araboza (1) | 2025.01.15 |
---|---|
[트러블 슈팅] 키오스크 과제 with Java (0) | 2025.01.13 |
[트러블 슈팅] CH 계산기 과제 with Java (1) | 2025.01.07 |
[Java] try - catch - finally 의 사용법 with Spring boot (2) | 2025.01.03 |
[Java] Arrays.sort() vs Arrays.stream() 에 대해서.araboza 1편 (1) | 2024.12.27 |