본문 바로가기

TIL

TIL16-계산기 3차

1. gui, 후위변환,계산,우선순위, 연산자 묶음으로 클래스로 분리

  • 구성
더보기
전체적인 분류
주요 코드 분류

 

  • 부족한 점
더보기

전체적인 구성을 할때부터 void로 많이 나누었기때문에 큰 문제가 안되겠다고 판단했지만, 클래스 ,enum,인터페이스와 같이 객체가 존재하고 그 마다 역할이 달라서 class로만 통일한 부분에 있어서 이게 맞는 판단인지 불분명하다.

또한 더욱 세세하게 나눌것인가와 어떻게 하면 코드를 단축시키기고  가독성을 느릴 것 인가에  있어서 부족함이 많이 느꼈으며,lv3를 진행할 땐 좀 더 가독성에 중점을 두기 위해 공부를 할 필요를 느꼈다.

2. 예외처리

  • 목적 EmptyStackException과ArithmeticException 예외처리
  • why?  EmptyStackException은 아무것도 입력되지 않아서 발생하는 문제,  ArithmeticExceptio은 무한의 값을 가지는 경우의 문제이기 때문에 exception이 발생한다. 이를 예외처리하여 사용하였을 때 문제에 대해 인지하고 해결하기 위해서 이다.  
  • 구성
더보기
public class Solution extends Calculator{
    private String solute(String str) {
        Stack<Double> stack = new Stack<>();
        stack.push(0.0); // 음수 연산 방지용 (0을 미리 넣어둠)

        String[] tokens = str.split(" "); // 공백을 기준으로 후위 표기법 토큰 분리

        for (String token : tokens) {
            if (token.matches("-?\\d+(\\.\\d+)?")) { // 공부
                stack.push(Double.parseDouble(token));
            }
            else { // 연산자인 경우
                Double firstInt;
                Double secondInt;
                try {
                    secondInt = stack.pop();
                    firstInt = stack.pop();
                    stack.push(releaseCalculation(firstInt, secondInt, Operator.fromChar(token.charAt(0)))); // 연산 결과를 스택에 다시 푸시
                } catch (EmptyStackException e) {
                    return "입력이 필요합니다";
                } catch (ArithmeticException e) {
                    return "0으로 나눌 수 없습니다";
                }
            }
        }
        return Double.toString(stack.pop()); // 최종 결과 반환
    }
  • 트러블 슈팅
더보기

배경 : 예외처리와 예외발생시 사용자에게 알리는 방식에 대한 고민

 

발단 : 간단하게 예외발생 시 print를 사용해서 문제를 알렸지만 이가 부자연스럽다고 생각하였습니다.

 

전개:  각각에 연산에서 예외 발생시 textfiled를 해당하는 문자를 return을 하는 함수를 고민하였지만 코드가 길어짐

으로 가독성이 안 좋아진다는 문제를 확인하였습니다.

 

절정 : solution에서 rerurn을 하면 String 자료형으로 return된다는 사실을  알게됐고, 이를 사용한다면 textfiled를 원하는 형태로 알림이 가능해진다것을확인 적용하였습니다.

 

결말 : 예외처리는 상위 객체에서도 가능하다는 사실과 rerurn 값이 어디로 rerurn이 되는지에 대한 확인은 코드를 불필요하게 늘릴필요가 없으므로 꾸준한 체크는 필수라는 것을 잊으면 안된다.!!!!

 

3. enum

  • 목적 enum을 사용하여 연산자를 클래스로 분리하기
  • why?  enum을 사용하여 가독성을 높이며,타입 안정성을 높이기 위해서이다.
  • 구성
더보기
enum Operator {
    ADD('+'), SUBTRACT('-'), MULTIPLY('*'), DIVIDE('/'), MODULO('%');

    private final char symbol;

    Operator(char symbol) {
        this.symbol = symbol;
    }

    public char getSymbol() {
        return symbol;
    }

    public static Operator fromChar(char ch){
        for(Operator operator: Operator.values()){
            if(ch == operator.symbol){
                return operator;
            }
        }throw new IllegalArgumentException("계산기에 존재하는 수식이 아닙니다.");
    }

}
  • 부족한 점

일단 enum에 대한 이해가 부족하기에 제대로된 사용이 되었다고는 어렵다. 왜냐하면 enum을 사용하여 메소드를 포함시키는 경우도 존재하며 또한 다른 케이스에선 적용이 어렵다는 점을 미뤄봤을 때  추후에 다시 볼 때 부족한 점을 찾을 수 있

을 것이라고 추측되기 때문이다.

 

4. 합계,평균

  • 목적 결과창을 통하여 stream&lamda 사용한 합계,평균 표현 
  • why?  stream&lamda을 통해 해당 기능에 코드를 간략하게 표현이 가능하고, stream&lamda을 사용함으로 본인의 실력을 늘릴 수 있는 기회이기 때문이다. 
  • 구성
더보기
package lv3;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.List;

import static java.util.stream.Collectors.joining;

public class GuiSetting extends JFrame implements ActionListener, KeyListener {
    // 입력 필드 관련 변수
    String textField = ""; // 연산식을 저장하는 문자열
    JTextField jTextField = new JTextField(textField, 29); // 메인 입력 필드
    JTextField resultField = new JTextField(textField, 3); // 최근 계산 결과 표시 필드
    JTextArea resultsField = new JTextArea(10, 32); // 모든 결과 기록을 표시하는 텍스트 영역
    Postfix postfix = new Postfix();
    Solution solution = new Solution();

    // 창의 제목
    String title = "사칙 연산 계산기";

    // 계산 결과 저장 리스트
    List<String> storeList = new ArrayList<>();

    // 결과값을 누적할 문자열
    String storeListor = "";


    // 버튼 클릭 이벤트 처리
    @Override
    public void actionPerformed(ActionEvent e) {
        String button = e.getActionCommand(); // 클릭된 버튼의 텍스트 가져오기

        if (button.equals("AC")) { // 전체 지우기
            textField = "";
            jTextField.setText(textField);
            storeListor="";
            storeList=new ArrayList<>();
            resultsField.setText("");
            resultField.setText("");
        }
        else if (button.equals("=")) { // 계산 실행
            String yardesult = postfix.releasePostfix(textField); // 중위 → 후위 변환
            textField = solution.releaseSolution(yardesult);// 후위 연산식 계산 결과 반환
            jTextField.setText(textField);
            if (jTextField.getText().equals("입력이 필요합니다")||jTextField.getText().equals("0으로 나눌 수 없습니다")){
                textField = "";
            }
            else{
                storeList.add(textField); // 계산 결과 저장
                resultField.setText(storeList.get(storeList.size() - 1)); // 최근 결과 표시
                textField = "";
            }
        }
        else if (button.equals("결과값")) { // 저장된 결과값 출력
            storeListor=storeList.stream().collect(joining("\n"));
            resultsField.setText(storeListor);
        }
        else if (button.equals("최근 결과 삭제")) { // 마지막 결과 삭제
            if (!storeList.isEmpty()) {
                storeList.remove(storeList.size() - 1);
//                storeListor=storeList.stream().reduce("",(a,b)->a+"\n"+b);//스트림 람다 적용
                storeListor=storeList.stream().collect(joining("\n"));
                resultsField.setText(storeListor);
            }
        }
        else if (button.equals("C")) { // 한 글자 지우기
            if (!textField.isEmpty()) {
                textField = textField.substring(0, textField.length() - 1);
                jTextField.setText(textField);
            }
        } else if (button.equals("합계")) {
            if (!storeList.isEmpty()){
                double sumList = storeList.stream().mapToDouble(Double::parseDouble).sum();
                resultsField.setText(Double.toString(sumList));
                System.out.println("sumList = " + sumList);
            }

        } else if (button.equals("평균")) {
            if (!storeList.isEmpty()) {
                double avgList = storeList.stream().mapToDouble(Double::parseDouble).average().getAsDouble();
                resultsField.setText(Double.toString(avgList));
                System.out.println("avgList = " + avgList);
            }

        } else if (button.equals("최근값")) {
            if(!storeList.isEmpty()){
                textField=storeList.get(storeList.size()-1);
                jTextField.setText(textField);
            }
        } else { // 숫자 및 연산자 입력
            textField = textField.concat(button);
            jTextField.setText(textField);
        }
    }

    // 키 입력 이벤트 처리 (타이핑)
    @Override
    public void keyTyped(KeyEvent e) {
        char keyChar = e.getKeyChar();
        if (Character.isDigit(keyChar) || "+-*/%()".indexOf(keyChar) != -1) {
            textField = textField.concat(String.valueOf(keyChar));
            jTextField.setText(textField);
        }
        e.consume(); // 기본 입력 차단
    }

    @Override
    public void keyReleased(KeyEvent e) {
    }

    @Override
    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_BACK_SPACE) { // 백스페이스 처리
            if (!textField.isEmpty()) {
                textField = textField.substring(0, textField.length() - 1);
                jTextField.setText(textField);
            }
        } else if (e.getKeyCode() == KeyEvent.VK_ENTER) { // 엔터 입력 시 계산 실행
            String yardesult = postfix.releasePostfix(textField); // 중위 → 후위 변환
            textField = solution.releaseSolution(yardesult);// 후위 연산식 계산 결과 반환
            jTextField.setText(textField);
            textField = "";
        }
        e.consume(); // 기본 입력 차단
    }
}
  • 트리블 슈팅
더보기

배경 : 스트림과 람다에 적용방식

 

발단 : 아래에 있는 블로그를 살펴보면서 기능에 맞는 스트림과 람다를 적용

 

전개:  우선적으론 join을 사용하였는데 추가적인 검색으로 찾을 예외처리가 가능한  reduce를 발견하였다 

 

절정 : !storeList.isEmpty())를 우선적으로 처리하기 때문에 storeList가 null이 가능성이 적다고 판단하였고, 통일성을 위해 join을 선택하였다. 이 선택이 맞는지는 지금도 잘 모르겠다.

 

결말 : 기능적으로 예외처리를 함으로써 사용자에게 표현 해줄 수 있다면 !storeList.isEmpty()을 하는 선택은 옳은 선택이 아니라고 판단이 된다. 내가 사용하였을 때 원인을 모르면 답답한 경험을 겪어봤기에 다음과 상황(예외처리를 막아버리는 상황과 예외처리를함으로 표현하는 상황)들을 고려함과 동시에 thread를 어떻게 사용하는지를 고민하게 됐다.

5. 참조

더보기

https://velog.io/@mooh2jj/Java-Enum%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0

 

[Java] Enum을 사용하는 이유와 활용법

Java enum은 제한된 값 목록을 갖는 타입입니다. enum은 다음과 같은 이점을 갖습니다.enum은 컴파일 타임에 타입 안정성을 보장합니다. 특정 범위의 값만 사용 가능하므로 컴파일 오류나 런타임 예

velog.io

https://hbase.tistory.com/166

 

[Java] 열거 타입(Enum) 사용법 및 예제

프로그래밍 언어에서는 열거 타입을 제공하는 경우가 많다. 요일이나 계절처럼 한정된 개수의 값들을 하나로 묶어서 사용하고 싶을 때, 열거 타입을 사용하면 편하다. 자바에서는 Enum 타입을 통

hbase.tistory.com

https://dd-developer.tistory.com/99

 

[JAVA] 스트림은 주의해서 사용하자

스트림 (Stream) 스트림은 다량의 데이터 처리 작업을 위해 자바 8에서 등장했다. 컬렉션 처리를 위한 새로운 API라고 할 수도 있다. 스트림 구조 스트림은 초기 데이터→ 중간 연산 → 최종 연산 의

dd-developer.tistory.com

https://bombichun.tistory.com/entry/JAVA%EB%9E%8C%EB%8B%A4%EC%99%80-%EC%8A%A4%ED%8A%B8%EB%A6%BCLambda-Stream

 

[JAVA]람다와 스트림(Lambda & Stream)

1. 람다식(Lambda Expression) 1.1. 람다식이란? - 함수(메서드)를 간단한 식으로 표현하는 방법 - 익명 함수(이름이 없는 함수) - 함수와 메서드의 차이 - 근본적으로 동일. 함수는 일반적 용어. 메서드는

bombichun.tistory.com

 

 

6. github

 

'TIL' 카테고리의 다른 글

TIL 17 -코드 가타:  (0) 2025.03.06
TIL 15 계산기 2차  (0) 2025.03.04
TIL14 계산기-1차  (0) 2025.02.27
TIL-13 자바의 프로젝트 관리(이름, 패키지,클래스)  (0) 2025.02.27
TIL12 - 컴퓨터의 기억방식  (1) 2025.02.26