[내일배움캠프] Java CH2 - 계산기 만들기

변채주·2025년 9월 17일

Java

목록 보기
3/8
post-thumbnail

요구사항 분석 및 흐름도 제작

(25. 09. 16)

⚠️아래 흐름도는 초기 기획한 구성으로 프로젝트를 진행하면서 변경된 부분이 있음(25.09.19)


반복문으로 입력된 배열 arr에서 double, char 변수를 가져와서 연산 함수에 조건문으로 넣고(소수점 계산을 위해 double 자료형을 쓰기로 한다.)
연산 후 결과를 항상 current에 덧씌운다. 덧씌워진 current 값은 배열 arr의 맨 처음 자리[0]에 추가된다. 그리고 다시 새로운 값을 입력받으러 간다.
이 과정을 반복해서 계산을 이어가다가 배열에 current와 '=' 밖에 남지 않았을 때, current 값을 result에 입력해주고 계산을 마친다.
(0917, 애초에 배열을 전달할 때 "="를 빼고 전달하도록 한다. 계산을 쭉 이어나가면 결국 current만 남게 되니 메모리도 아끼고 값도 바로 Main으로 전달할 수 있다.)

클래스와 캡슐 구분

1. 계산 역할을 해줄 Calculator Class

연산 함수들이 이 클래스에 속하게 된다.

  • add(+)
  • minus(-)
  • multiply(*)
  • devide(/)
  • modular(%)
  • square(^)

2. 입력받은 계산식을 배열 형태로 담아줄 Array Class

  • char형으로 받은 숫자들을 string 형태로 다듬고 그걸 또 Double 형으로...

‼️ (트러블슈팅) Char 배열 → Double형

으로 바로 바꾸는 방법은 없을까?

(2025. 09. 17)

어제에 이어 배열 클래스를 정리해보도록 한다.
처음에는 Char 문자형으로 전체 계산식을 입력받아서 쪼개려고 했다. 그러나 문제가 있었으니...

1. 숫자+소수점+숫자 / 연산자 / "=" 조합의 복잡함.

쪼개진 퍼즐을 하나하나 맞추는 것보다 큰 조각을 나누는 게 작업 횟수가 적다.
→ 처음의 입력은 하나의 문자열로 받고, 연산자를 기준으로 쪼개기, 그리고 문자열을 실수형으로 형 변환 ⇒ 이렇게 만든 배열을 Calculator로 전달하는 방법을 택했다.

2. 입력 단위 설정

(아직은 콘솔 형태라 상관없지만 미리 정하자면)
여러 요소를 계산할 때 원래는 입력하는 족족 계산 결과 값을 화면에 띄우고자 했으나, 그렇게 하면 연산 과정이 반복되어서 처리가 많아지고 복잡하다.
그러니 '='가 입력되면 한번의 연산 과정을 거친 뒤 결과 값을 화면에 출력하기로 정했다.

3. 구현 이슈

  • 문자열+실수형으로 구성된 배열 만들기 Object[]
    Java는 정적인 배열을 주로 만들기 때문에 자주 쓰이지는 않는 배열 형태다.
    Object[]는 여러 자료형을 한 배열 안에 담을 수 있어 편하기는 하지만, 그만큼 안의 값을 꺼내 쓸 때도 형 변환을 해줘야 할 수도 있다.
    → 이번에는 그렇게 복잡한 구성이 아니기에 그냥 사용 하도록 했다.

  • 문자열 String → 실수형 Double 형 변환 Double.parseDouble()
    1번의 방식으로 배열을 만들다보니 문자열을 실수형으로 형 변환해주는 메서드가 필요했다. 검색으로 찾았음👍

  • 예외 처리
    배열을 생성하는 단계에서 예외 처리를 하려 했으나, 아직 Exception()을 사용하는 게 어려워서(자꾸 빨간줄이 뜬다) 그냥 Main Class에서 if 문으로 정리했다.

‼️(트러블슈팅) 형 변환의 영향

Main 클래스에서 에러를 처리하게 되면 문제가 있다. 바로 검사할 배열이 Object라는 것...

int i;
            for (i = 0; i < arrAfter.length - 1; i++) {
                if (arrAfter[i] == "/" && arrAfter[i + 1] == "0") {
                    System.out.println("ERROR");
                }
            }

안에 실수와 문자가 모르는 순서로 섞여 있기 때문에 배열[i]의 형이 어느 쪽일지 알 수 없는 상황에서 문자"/"일지 숫자 0일지 논리 비교를 하다가 오류가 나지 않을까 걱정이다.

(25. 09. 18)

아래 트러블슈팅을 진행하다가 이 부분⬆️에서 아니나 다를까 문제가 발생해서^^ 이어 적는다.

실행했더니 arrAfter[i]가 null이라 에러가 발생했다고 한다.
에러 처리를 주석처리하고 arrAfter를 출력해봤다.

???내 연산자 어디갔어222
연산자에만 나타나는 걸로 봤을 때 if문에 문제가 있다.

이게 원인이 아닐까? 아래 추천처럼 equals()로 바꿔봤다.
안되더라...
고민하다가
1)형 변환을 해 Object로 사용하는게 너무 복잡하고
2)어차피 Calculator, 즉 연산 과정에서 값을 호출할 때 또 형 변환을 해야하기 때문에
→ Object를 안 쓰고 String 배열 리스트로 만들기로 했다.
그래서 수정된 코드

public ArrayList<String> makeArray(String arrBefore) {
        //문자열을 연산자 기준으로 나누기 + 연산자도 포함시키기
        StringTokenizer arrMiddle =  new StringTokenizer(arrBefore, "+-*/%^=", true);
        //배열 리스트 생성 + 연산자로 구분된 인덱스 집어넣기
        ArrayList<String> arrAfter = new ArrayList<>(arrMiddle.countTokens());
        int i = 0;
        while (arrMiddle.hasMoreTokens()) {
            String token = arrMiddle.nextToken();
            if(token.equals("=")) {
                break;
            }
            else {
                arrAfter.add(i, token);}
            i++;
        }
        return arrAfter;
    }

‼️(트러블슈팅) 제곱근 연산

어차피 num1^ 뒤에는 2가 올테니까 num1 * num1으로 적으면 되겠지?
라는 안일한 생각으로 코드를 작성했었으나 배열 클래스를 적다보니 '^' 뒤에 올 2가 배열의 자리를 차지하고 있으니 연산 순서를 정할 때 오류가 발생할 가능성이 있다는 생각이 들었다.

public double square(double num1, double num2){
        //수정 전
        //current = num1 * num1;
        
        //수정 후
        current = Math.pow(num1, num2);
        return current;
    }

그래서 '^' 뒤에 있는 실수를 하나 더 받아서 그 횟수(num2)만큼 num1을 제곱하는 걸 구현하려고 했는데 검색하니까 아주 편리하게 메서드가 있더라.
Math.pow()를 써서 current에 결과가 담기도록 수정했다.

‼️(트러블슈팅) 배열에 연산자가 사라진 사건

연산 기능을 더 손보기 전에 배열이 어떻게 출력되는지 볼려고 ArrayFormula에서 넘겨준 arrAfter 배열값을 Main 클래스에서 출력해봤다.

(Whyrano...whyrano.....!)
내 연산자 어디갔어111

거꾸로 코드를 거슬러 올라가는연어처럼확인해보니 연산자를 없애는 범인은 아래 코드였다.

String[] arrMiddle = arrBefore.split("[+\\-*/%^=]");

알고보니 split은 구분자- 분리할 기준으로 삼을 문자, 즉 () 안에 입력되는 내용) -을 배열에 포함시켜주지 않는단다.

(발췌-WEBSTORYBOY)

split 대신에 쓸 수 있는 메서드를 찾아서 검색을 했는데 AI가 찾아주더라...

편한 세상이 되었다.

StringTokenizer는 아래와 같이 구성되어있다.

public class StringTokenizer implements Enumeration<Object> {
    private int currentPosition;
    private int newPosition;
    private int maxPosition;
    private String str;
    private String delimiters;
    private boolean retDelims;
    private boolean delimsChanged;

안에는 Double 형이 없다. 일단은 연산자를 기준으로 문자열이 나눠지는 방법이기 때문에 이후 과정은 여전히 String → Double 로 형 변환이 필요하다. 현재 단계에선 신경쓸 일이 아니니 코드를 수정하고 혹시 몰라 연산자까지 잘 return 해주는지 확인해봤다.

(헷갈릴 수 있으니 토큰 사이사이에 " "공백을 넣어줬다.)
소수점 숫자가 입력되면?

잘 된 다 Good~!
그러나 이 배열의 문제는 위의 (트러블슈팅)형 변환의 영향으로 이어지게 된다...

<건너뛰어서> 연산 클래스에서 벌어진 일

‼️(트러블슈팅) 모든 연산자가 예외 처리


IndexOutOfBoundsException 오류라고 한다...

if (arrAfter.contains("+")) {
                    operator = '+';
                    n = arrAfter.indexOf("+");
                    double num1 = Double.parseDouble(arrAfter.get(n - 1));
                    double num2 = Double.parseDouble(arrAfter.get(n + 1));
                    calc.CalculatePackage(num1, num2, operator);
                    arrAfter.set(n, String.valueOf(current));
                    arrAfter.remove(n - 1);
                    arrAfter.remove(n + 1);

원인은 맨 아래에 있는 arrAfter.remove(n+1);

여기서 arrAfter.remove(n-1); 이 실행되면서

아래 배열처럼 된 상황에서 내가 존재하지 않는 n+1 인덱스를 호출하면서 삭제하겠다고 말한거다. = arrAfter.remove(n+1);

그리고 추가로, 모든 연산자가 Switch문에서 예외처리로 빠지는 문제가 있었다.
이건 내가 잘못 적은게 원인인데 Calculation.java를 Child 클래스로 만들어서 Calculator.java를 부모 클래스로 상속받도록 했다.
그러면서 switch문에 () 안에 들어갈 연산자 객체를 부모 클래스에 있는 연산자 char로 가져온 것이다.
→ Main에서 내부 클래스의 객체를 불러와 내용을 판별하도록 수정해줬다.

//수정 전 switch (super.operator) { ... }
switch (operator) {
            case '+':
                current = super.add(num1, num2);
                break;
                ... }

일단 오류없이 숫자가 나오는 걸 확인했다.
결과값이 제대로 나오지 않는 건 우선순위를 잘못 설정한 탓이니...
To Be Continued➡️

(25. 09. 19)

‼️(트러블슈팅) 틀린 연산 결과

위 문제를 짤막하게 30분만에 해결했기에 글로만 남긴다.
조건문을 살펴봐도 연산 우선순위는 논리상 문제가 없었다.

while 반복문 안에 계산 과정을 출력해서 확인해보니 1회의 계산 결과를 담고있어야 할 current가 계속 0.0을 나타내고 있었다. 호출한 메서드의 값이 current에 들어가지 못하고 어딘가를 헤매고 있다는 뜻...
if문 내부를 확인해보니 'current = '연산Method 같이 current에 담는 문구를 적지 않았다. (애초에 연산 Method를 작성할 때 상속 개념을 이해하려고 가볍게 만든 터라 반환(return)이 없는 void 타입으로 설정했다.)
→ 그래서 연산 Method의 타입을 current와 맞게 Double 타입으로 수정하고,
내부에도 current를 반환하도록 각 switch 문에 return current;를 추가했다.
그리고 Main 클래스의 if문에도 각각 반환된 current 값을 가져올 수 있도록 위에 적힌대로 수정했다.

전체 프로젝트 회고

1. 초기 기획에서 변경된 점

  • GUI 구현 삭제 → AC 기능도 삭제
    사유 : 콘솔 내에서 계산기가 동작하도록 방식을 변경
  • 결과값에 대한 계산을 반복할 수 있도록 로직 구성

2. TIL 요약

(반성 필요) 과제 가이드를 활용하지 않았음

  • 제시된 Lv1~3 가이드를 확인하지 않고
    Lv0의 생각해보기 → 요구사항 정의 → 설계(위 흐름도 제작) → 아래 내용 읽지 않고 바로 코드 작성

(그럼에도 불구하고?) 요구사항과 목적을 일부 달성함(내가해냄)

  • (처음부터)Double 형의 숫자 연산이 가능하도록 구현함
  • 클래스 적용하기 - Calculator, Calculation 클래스로 연산 역할 구분, 상속을 사용하기
  • 컬렉션 중 ArrayList 사용하기
  • Main 클래스에서 삭제 메서드를 사용해 계산을 정리하기

(개선점) 빼먹은 목표

  • Getter/Setter 메서드 사용하지 않았음
  • 중간 연산 결과를 저장하는 컬렉션 필드를 Main 클래스에서 분리하지 않았음
  • Enum, 제너릭, 람다, 스트림을 활용하지 못했음
    • (Enum)연산자 타입 정보 관리 ❌ → Main 클래스에서 조건문(if)로 관리했음
    • (람다, 스트림)저장된 연산 결과 중 입력된 값보다 큰 결과값을 조회하는 메서드 작성 ❌ → 연산 결과를 저장하지 않았어서 변경할 부분이 多 + 개념 공부 필요
profile
우당탕탕얼레벌레 개발 일지

0개의 댓글