Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

이지은님의 블로그

250109 - Java 계산기 Lv.3 구현과 트러블슈팅: 제네릭, 람다와 스트림, 조회 기능 클래스화, Enum 연산 통합(Predicate<T>) 본문

TIL

250109 - Java 계산기 Lv.3 구현과 트러블슈팅: 제네릭, 람다와 스트림, 조회 기능 클래스화, Enum 연산 통합(Predicate<T>)

queenriwon3 2025. 1. 9. 20:21

▷ 계산기 작성 과정

계산기 Lv.3를 어떻게 작성했는지 과정을 써보면서 트러블슈팅을 한 과정을 작성해보려고 한다.

 

 

<<목차>>

1.계산기 작성 초안(ver.1)

2. 제네릭을 사용해보기

3. 람다와 스트림을 사용하여 계산결과 조회하기

4. 이후 입력받은 값보다 큰 값을 출력하도록 했을 때

5. 조회기능을 클래스화 + 메서드 기능 분리

6. enum과 추상클래스의 연결

7. enum과 loopup가능연산자들과 연결

8. 조회 기준에 따른 코드 반복 줄이기(Interface Predicate<T>)

9. 함수형 인터페이스 - static 메서드에서 this.operator 사용하기(트러블 슈팅)

10. 계산기 기능추가하기 (나머지, 제곱 연산)

11. lookup에서 parser를 빠져나오면 형관련 오류가 발생함(트러블슈팅)

 

 

 

 


 

 

1. 계산기 작성 초안(ver.1)

먼저 생각나는대로 계산기를 작성해보았다. 

//main.java
package com.example.Calculator3;

public class Main {
    public static void main(String[] args) throws Exception {
        CalculatorApp app = new CalculatorApp();
        app.run();
    }
}

 

메인(main.java)에서는 오직 CalculratorApp만 실행될 수 있도록 만들었다.

이유: 메인에서 실행이 되는데, 메인은 어떠한 행동을 한다는 뜻보다는 제일 처음에 실행되는 코드라는 뜻으로 실행 시작 내용만 구현하였다.(CalculratorApp 실행하는 시작점)

 

// CalculatorApp.java
package com.example.Calculator3;

import java.util.Scanner;

public class CalculatorApp {

    public void run() throws Exception{
        Scanner scanner = new Scanner(System.in);
        Parser parser = new Parser();
        Calculator calculator;

        while (true) {
            try{
                System.out.print("계산 식을 입력하세요(숫자 연산자 숫자)>> ");
                String ans = scanner.nextLine();

                if(ans.equals("exit")) break;

                calculator = parser.StringOperation(ans);

                switch (calculator.getOperator()){
                    case '+':
                        calculator.setOperate(new AddOperation()); break;
                    case '-':
                        calculator.setOperate(new SubtractOperation()); break;
                    case '*':
                        calculator.setOperate(new MultiplyOperation()); break;
                    case '/':
                        calculator.setOperate(new DivideOperation()); break;
                }

                double result = calculator.calculate();
                if(Double.isNaN(result)){
                    throw new printHowException("[오류] 계산이 불가합니다.");
                }
                System.out.println(result);

            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
        }
        System.out.println("계산기를 종료합니다.");
    }
}

 

CalculatorApp.java는 계산기의 진행을 돕는다. 계산기는 입력,종료판단, 검사(Parser), setOperator, 계산(Calculator), 결과값 출력으로 나누어져있으며 이 과정을 반복할 수 있도록 해주었다.

만약 과정에서 예외가 발생한다면 예외에 관한 안내를 출력한다.

여기서 한가지 짚고 넘어가자면, 연산 판단을 switch문으로 한다는 것이다.(나중에 고칠 예정이다)

 

 

// Parser.java
package com.example.Calculator3;
import java.util.regex.Pattern;

public class Parser {
    private static final String NUMBER_REG = "^[0-9]+$";

    public ArithmeticCalculator StringOperation(String ans) throws printHowException{
        String[] ansArray = ans.split(" ");

        if(ansArray.length != 3){
            throw new printHowException("[오류] 입력형식을 확인해주세요.");
        }

        char operator = OperatorCheck(ansArray[1]);
        double firstNumber = NumCheck(ansArray[0]);
        double secondNumber = NumCheck(ansArray[2]);

        return new ArithmeticCalculator(operator,firstNumber,secondNumber);
    }

    public char OperatorCheck(String ans){

        char operator = ans.charAt(0);
        if(!OperatorType.checkOperator(operator)){
            throw new printHowException("[오류] 연산자는 +-*/만 사용가능합니다.");
        } else{
            return operator;
        }
    }

    public double NumCheck(String ans){
        if(!Pattern.matches(NUMBER_REG, ans)){
            throw new printHowException("[오류] 연산할 값을 입력하세요.");
        } else{
            return Double.parseDouble(ans);
        }
    }
}

입력한 값을 나누고 의도에 맞게 입력되었는지 확인하는 클래스이다. 의도한 입력에 맞도록 확인하기 위해 연산자는 enum, 숫자는 Pattern.matches 사용했다.

 

 

// OperatorType.java
package com.example.Calculator3;

public enum OperatorType{
    ADD('+'), SUBTRACT('-'), MULTIPLY('*'), DIVIDE('/');

    private char operator;

    OperatorType(char operator) {
        this.operator = operator;
    }

    public char getOperator(){
        return operator;
    }

    public static boolean checkOperator(char operator){
        for(OperatorType op : values()){
            if(op.getOperator() == operator){
                return true;
            }
        }
        return false;
    }
}

연산자를 정의한 enum이다. 입력받은값이 enum에 해당되지 않으면 false를 반환한다.

(여기서 수정하고 싶은 사항이 발생한다. 추상클래스로 생성한 각 연산클래스를 enum에서 매칭할 수 있지 않을까? 그렇게 한다면, 연산의 확장에도 용이할 것으로 생각된다. 도전해보고 싶은 과제다)

 

 

//ArithmeticCalculator.java
package com.example.Calculator3;

public class ArithmeticCalculator {
    private char operator;
    private double firstNumber;
    private double secondNumber;

    private AbstractOperation operate;

    public Calculator(char operator, double firstNumber, double secondNumber) {
        this.operator = operator;
        this.firstNumber = firstNumber;
        this.secondNumber = secondNumber;
    }

    public char getOperator() {
        return operator;
    }

    public void setOperate(AbstractOperation operate){
        this.operate = operate;
    }

    public double calculate(){
        double result = operate.operation(firstNumber,secondNumber);
        return result;
    }

}

오로지 연산만 담당하는 클래스이다.(Calculator.java에서 ArithmeticCalculator.java로 변경)

 

 

여기까지 ver.1 인데(단순히 추상클래스(코드스니펫은 생략했다.) + parser, enum만 구현했다.) 수정하고싶은 부분은 다음과 같다.

  1. enum기능 확장하기(연산 클래스도 함께 매핑해줄 있도록)
  2. 제네릭타입을 사용하기

 

 

 

 

2. 제네릭을 사용해보기

 

제네릭의 사용이 잘 이해가 되지 않아 참고로 한 블로그가 있다.

https://velog.io/@sjhak8034/generic%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%98%EC%97%AC-%EA%B3%84%EC%82%B0%EA%B8%B0-%EC%97%85%EA%B7%B8%EB%A0%88%EC%9D%B4%EB%93%9C%ED%95%98%EA%B8%B0

 

generic을 활용하여 계산기 업그레이드하기

lv2. 문제까지는 input으로 정수만 받아왔다면, 이번 문제는 정수 뿐만 아니라 실수까지 input으로 받는 코드를 구현하는 것이다.calculator class안에 있는 calculate메소드를 generic으로 바꾸었다. 이전에

velog.io

 

 

먼저 제네릭을 사용하는 이유는 다음과 같다.

1. 제네릭을 사용하면 잘못된 타입이 들어올 수 있는 것을 컴파일 단계에서 방지할 수 있다.

2. 클래스 외부에서 타입을 지정해주기 때문에 따로 타입을 체크하고 변환해줄 필요가 없다. 즉, 관리하기가 편하다.

3. 비슷한 기능을 지원하는 경우 코드의 재사용성이 높아진다.

 

결국 재사용성을 높이기 위해 외부에서 자료형을 지정해준다는 것이다.

 

그럼 이것을 어떻게 계산기에 적용할 수 있을까

 

계산기 과제 요구사항은 다음과 같다. 

 

실수, 즉 double 타입의 값을 전달 받아도 연산이 수행하도록 만들기

- 키워드 : 제네릭
    - 단순히, 기존의 Int 타입을 double 타입으로 바꾸는 게 아닌 점에 주의하세요!
- 지금까지는 ArithmeticCalculator, 즉 사칙연산 계산기는 양의 정수(0 포함)를 매개변수로 전달받아 연산을 수행
- 피연산자를 여러 타입으로 받을 수 있도록 기능을 확장
    - ArithmeticCalculator 클래스의 연산메서드(calculator)
- 위 요구사항을 만족할 수 있도록 ArithmeticCalculator 클래스를 수정합니다. (제네릭)
    - 추가적으로 수정이 필요한 다른 클래스나 메서드가 있다면 같이 수정해주세요.

 

그러니 계산기 안에서 제네릭을 작성하여 double형이나 int형이나 자유롭게 받을 수 있도록 하라는 뜻인 것 같다.

그럼 parser클래스를 제네릭으로 만들어서 자유롭게 문자열을 판단하여 숫자 자료형으로 받아들이는게 일반적인 과제 수행방법이라고 생각했다.

 

 

 

수정 전 코드
//Parser.java

import java.util.regex.Pattern;

public class Parser <T extends Number>{
    private static final String NUMBER_REG = "^[0-9]+$";

... 생략

    public T NumCheck(String ans){
        if(!Pattern.matches(NUMBER_REG, ans)){
            throw new printHowException("[오류] 연산할 값을 입력하세요.");
        } else{
            return (T) Double.valueOf(ans);
        }
    }
}

그럼 이렇게 해보자. 이럴 경우 리턴값 또한 애매해진다. 상황에서는 확실하게 Number자료형을 받는 것이 넓은 범위의 수를 다룰 있다고 생각하여 수정하는 맞는 것같다고 판단했다.

 

수정 후 코드
import java.util.regex.Pattern;

public class Parser <T extends Number>{
    private static final String NUMBER_REG = "^[0-9]+$";

... 생략

    public Number NumCheck(String ans){
        if(!Pattern.matches(NUMBER_REG, ans)){
            throw new printHowException("[오류] 연산할 값을 입력하세요.");
        } else{
            try{
                java.math.BigDecimal number =new BigDecimal(ans);
                
                if(number.scale() > 0){
                    return number.doubleValue();
                }else {
                    return number.intValue();
                }
            } catch (Exception e){
                throw new printHowException("[오류] 연산할 값을 입력하세요.2")
            }
        }
    }
}

과제의 의도는 문자열을 Int형태로 받으면 Int형으로 받고, double형태로 받으면 double로 받는 것이라고 판단했다.

그러면 의도 그대로 BigDecimal 클래스를 사용해서 실수면 double로 리턴, 정수면 int로 리턴하도록 했다. 그리고 이것을 Number 형태로 리턴한다.

 

여기서 문제가 하나더 발생하는데 실수를 입력했을때 정규표현식에서 통과시키지 못한다는 한계가 발생한다. 당연히 정규표현식 자체가 정수만을 가리키고 있기 때문에 실수를 처리할 수 없다. 여기서 정규표현식 사용을 포기하게 된다.(정수만 입력받는다면 가능)

 

참고로 실수값 정규식은 다음과 같다. 이렇게 길어질 거라면 굳이 해줄 필요성이 없다...

^[+-]?([0-9]*\.[0-9]+|[0-9]+\.[0-9]*|[0-9]+)$

 

 

필요없는 정규표현식까지 삭제하면 다음과 같이 parse 수정할 있다.

 

다시 수정...(정규표현식 삭제)
public Number NumCheck(String ans) {
    try {
        BigDecimal number =new BigDecimal(ans);

        if(number.scale() > 0) {
            return number.doubleValue();
        }
        return number.intValue();
        
    } catch (Exception e) {
        throw new printHowException("[오류] 연산할 값을 입력하세요.");
    }
}

 

제네릭을 사용한 연산도 함께 가져가고 싶어 추상클래스와 자식클래스도 함께 수정했다.(블로그를 참고했다.)

// AbstractOperation.java
package com.example.Calculator3;

abstract class AbstractOperation<T extends Number, U extends Number> {
    abstract double operate(T first, U second);
}

class AddOperation extends AbstractOperation{
    @Override
    double operate(Number first, Number second) {
        return first.doubleValue() + second.doubleValue();
    }
}

(위 형태로 다른 자식클래스들도 형태를 동일하게 맞췄다.)

 

 

 

 

 

3. 람다와 스트림을 사용하여 계산결과 조회하기

람다식과 스트림을 사용하기 위해서는 List를 사용해서 입력값을 저장해야한다. List에 한 연산씩(ArithmeticCalculator)를 클래스의 형태로 저장하여서 꺼내쓸 수 있으면 될 것 같다.(컬렉션 사용)

 

이후 람다식과 스트림을 이용하여 필터링하고 꺼내서 이전의 계산결과를 조회하는 방식이 좋을 같다는 생각을 했다.

 

이런식으로 처음에는 CalculatorApp 안에서(클래스 및 메소드를 새로 파서 기능분리를 하지 않고) 연산자에 따른 계산결과조회를 했다.

스트림을 사용해서 조회 조건을 필터링하여 이릉 foreach 출력하는 방식으로 간단하게 작성했다.

 

구현 결과

 

이렇게 이전에 계산결과를 도출했던 연산들을 연산자를 조건으로 출력할 있다.

 

 

 

 

 

4. 이후 입력받은 값보다 큰 값을 출력하도록 했을 때

본래 과제 목적은 입력받은 값에 대한 조회를 하는 것이었다. 연산자를 통한 검색도 과제 응용중에 하나이니 일단 남겨두고 이와 같은 방법으로  입력받은 값보다 값을 출력하는 것도 옵션에 넣어 코드를 작성해보았다.

else if(ans.equals("listup")){

    System.out.print("조회하고 싶은 기준을 입력하세요(1:연산자, 2:결과값)>> ");
    int listupAns = scanner.nextInt();
    scanner.nextLine();

    if(listupAns == 1){
        System.out.print("조회하고 싶은 연산자를 입력하세요>> ");
        String operatorAns = scanner.nextLine();

        switch (operatorAns){
            case "+":
                System.out.println("덧셈 조회"); break;
            case "-":
                System.out.println("뺄셈 조회"); break;
            case "*":
                System.out.println("곱셈 조회"); break;
            case "/":
                System.out.println("나눗셈 조회"); break;
            default:
                throw new printHowException("[오류] 잘못된 연산자 입니다.");
        }
        operationList.stream().filter(cal -> cal.getOperator().equals(operatorAns))
                .forEach(cal -> System.out.println(cal.toString()));
        System.out.println();

    } else if(listupAns == 2){
        System.out.print("조회하고 싶은 결과값을 입력하세요>> ");
        double resultAns = scanner.nextDouble();
        scanner.nextLine();

        operationList.stream().filter(cal -> cal.getResultNumber() > resultAns)
                .forEach(cal -> System.out.println(cal.toString()));
        System.out.println();

    } else{
        throw new printHowException("[오류] 잘못된 입력입니다.");
    }

}

 

동작과정은 먼저 listup을 입력했다면 

어떤 조건으로 출력할 것인지 1(연산자 기준) 또는 2(결과값 기준)로 입력을 받는다.

당연히 그 외의 값을 입력하면 예외상황으로 분류가 된다.

 

만약 1 입력했으면 앞서 구현했던 연산자 기준 조회 기능을 사용할 있고, 2 입력했으면 조회하고 싶은 결과값을 입력받을 있다. 해당 값을 기준으로 연산 데이터를 조회할 있다.

 

 

 

 

 

5. 조회기능을 클래스화 + 메서드 기능 분리

기능별로 클래스를 분리시키는 것이 확장면에서도 코드작성면에서도 유리할 것 같다는 생각을 했다. 그래서 조회기능을 따로 분리해서 클래스를 만들었다. 

거기에 그치지 않고 연산자 기준, 결과값 기준 조회에 대한 메서드 분리도 작업해보았다. 이렇게 해둔다면 기능별로 메서드를 사용할 있어서 확장 면에서 유리하다.

// CalculationLookup.java

package com.example.Calculator3;
import java.util.ArrayList;
import java.util.Scanner;

public class CalculationLookup {
    Scanner scanner = new Scanner(System.in);
    ArrayList<ArithmeticCalculator> operationList = new ArrayList<>();
    public CalculationLookup(){
    }

    public void insertOperationList(ArithmeticCalculator calculator){
        operationList.add(calculator);
    }

    public void run(){
        System.out.print("조회하고 싶은 기준을 입력하세요(1:연산자, 2:결과값)>> ");
        int loopUpAns = scanner.nextInt();
        scanner.nextLine();

        if(loopUpAns == 1){
            System.out.print("조회하고 싶은 연산자를 입력하세요>> ");
            String operatorAns = scanner.nextLine();
            lookupOperator(operatorAns);

        } else if(loopUpAns == 2){
            System.out.print("입력받은 값 이상의 결과값을 출력합니다>> ");
            double resultAns = scanner.nextDouble();
            scanner.nextLine();
            lookupResult(resultAns);

        } else{
            throw new printHowException("[오류] 잘못된 입력입니다.");
        }
    }

    public void lookupOperator(String operatorAns){
        switch (operatorAns){
            case "+":
                System.out.println("덧셈 조회"); break;
            case "-":
                System.out.println("뺄셈 조회"); break;
            case "*":
                System.out.println("곱셈 조회"); break;
            case "/":
                System.out.println("나눗셈 조회"); break;
            default:
                throw new printHowException("[오류] 잘못된 연산자 입니다.");
        }
        operationList.stream().filter(cal -> cal.getOperator().equals(operatorAns))
                .forEach(cal -> System.out.println(cal.toString()));
        System.out.println();
    }

    public void lookupResult(double resultAns){
        operationList.stream().filter(cal -> cal.getResultNumber() > resultAns)
                .forEach(cal -> System.out.println(cal.toString()));
        System.out.println();
    }
}

 

조회 기능 구현 완료

 

 

 

 

 

6. enum 추상클래스의 연결

 

이런식으로 문자열과 추상클래스를 상속한 서브클래스를 연결하는 방법을 생각해보고 싶었다. 이렇게 하면 switch를 사용하지 않고 enum자료형만 수정하고, 추상클래스를 상속받는 자식클래스를 추가하기만 하면 기능을 연산기능을 추가 시킬 수 있을 것 같다고 생각했다.

 

Switch 구문은 연산자 조건을 하나씩 따져서 연산을 수행한다. 그렇기때문에 연산자를 매핑하면 switch구문을 쓰지 않아도 괜찮을 것이라고 생각했다.

 

 

enum에 요소를 추가하면 다음과 같이 확장시킬수있다.

중요한 것은 enum생성자에 매개변수를 추가해줘야한다는 거다.

 

 

OperatorType ot = OperatorType.checkOperator(calculator.getOperator());
calculator.setOperator(ot.getAbstractOperation());

다음과 같이 OperatorType에서 operator를 추출하여

이를 setOperator함으로써 연산 클래스를 매핑 시킬 수 있다.

 

이 방법을 사용하면 연산자마자 switch를 사용하지 않아도 된다. 거기에 다른 연산자를 추가 시킬때도 용이하게 확장 시킬 있다는 장점이 있다.

 

 

 

 

7. enum loopup가능연산자들과 연결

Switch 조회하는 클래스에도 사용하였다. 

 

수정 전 코드

 

이렇게 연산자를 판단하는 switch문은 길기도하고 enum 이용한 메서드로 정의 해뒀기 떄문에 굳이 switch구문을 필요가 없다. 그래서  Parser 만들어둔 메서드를 이용하여 코드의 반복을 줄였다.

 

수정 후 코드

 

 

 

 

 

8. 조회 기준에 따른 코드 반복 줄이기(Interface Predicate<T>)

 

조회코드를 검토하면서 다음과 같이 코드의 중복을 발견할 수 있었다. 이것을 어떻게 해결하면 좋을까를 고민했는게 함수를 일급객체로 다루는 방법이 생각났다. 바로 함수형 인터페이스다.

 

 

 

Interface Predicate<T>

참고한 문서

https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html

https://yeonyeon.tistory.com/200

https://7772.github.io/2021-05-29-lambda-functional-interface/

 

 

함수를 일급객체로 다루기 위해 사용하는 인터페이스 제네릭을 사용하여 타입을 지정한다.

위 블로그에서는 @FunctionalInterface 어노테이션을 사용하는데, 없어도 동작하지만 함수형 인터페이스 조건에 부합되는지 검사해주므로 사용하는 것이 좋다고 한다.(어노테이션에 대해서 알아보는 것도 좋을 것 같다.)

 

함수형 인터페이스 - 사용방법(1)
@FunctionalInterface
interface Predicate<T> {
    boolean test(T t);
}

public class Prac_predicate {
    public static void main(String[] args) {
        Predicate<Integer> predicate = (num) -> num < 10;
        System.out.println(predicate.test(5)); //true
    }
}

람다를 이용하여 함수를 객체화하고 test객체를 이용하여 출력하면 된다.

 

함수형 인터페이스 - 사용방법(2)
public class VehicleSUVPredicate implements Predicate<Vehicle> {
    @Override
    public boolean test(Vehicle vehicle) {
        return Vehicle.Type.SUV.equals(vehicle.getType());
    }
}

다음과 같은 방식으로 인터페이스를 상속받아 test를 작성할 수도 있다.(오버라이딩으로 test작성)

 

 

함수형 인터페이스의 메서드
메서드 설명 결과
test(T) 입력값에 대해 조건을 평가. 조건에 따라 true 또는 false 반환.
and(Predicate) 조건을 논리적 AND 결합. 참일 때만 true.
isEqual(Object) 특정 값과 입력값이 같은지 확인. 값이 같으면 true.
negate() 조건의 반대를 반환. 참이면 거짓, 거짓이면 .
or(Predicate) 조건을 논리적 OR 결합. 하나라도 참이면 true.

 

 

 

위 사용방법도 있지만 배운내용을 활용하기 위해서

https://queenriwon3.tistory.com/74

1-2) 람다와 스트림 사용하기 방법으로 코드의 반복을 줄여보기로 했다.

 

 

 

 

9. 함수형 인터페이스 - static 메서드에서 this.operator 사용하기(트러블 슈팅)

'com.example.Calculator3.ArithmeticCalculator.this' cannot be referenced from a static context

함수형 인터페이스를 사용하기 위해서 ArithmeticCalculrator 클래스에서 참과 거짓을 출력하는 메서드를 정적멤버로 생성하였다. 

정적멤버로 작성한 이유는 그런 예제를 배운 것도 있지만, 조회하는 클래스에서 따로 계산기 클래스를 호출하는 것이 기능적으로 맞지 않다고 생각했으며, 조회를 하려면 여러개의 ArithmeticCalculrator인스턴스를 일일히 호출하는 것이 번거롭게 동작하는 것이라고 생각했기 때문이다.

 

 

오류의 내용은 다음과 같다. 정적(static)메서드나 정적블록에서는 인스턴스 필드나 메서드에 접근 할 수 없다.

이유는 생각보다 간단하다.

정적멤버는 클래스가 로드되면 생성된다. 그에 비해 인스턴스멤버는 객체까지 생성이 완료되어야 생성가능하고 사용이 가능하다. 그러므로 객체 생성이 되지 않는 상태에서 정적맴버에서 인스턴스에 접근하려고하는 것이 불가능하다. 

 

 

1) non-static방법

이 오류를 해결하려면 non-static방법으로 호출하는 방법이 있다. 그러나 그렇게 하려면 인스턴스선언을 해야하므로 의도에 맞지 않다.

 

 

 

2) 매개변수로 ArithmaticCalculator 전달

이 방법도 좋으나, 이렇게 코드를 작성한 결과 조회하는 클래스에 계산기 ArithmeticCalculator 클래스 내용이 포함되어 버리니, 의도에 맞지 않고 각 클래스의 역할을 침해하는 것 같아서 아쉬웠다.

이렇게 트러블 슈팅 한 채로 더 쉬운 방법을 생각해보고자 한다.

lookupOperation 클래스안에서만 조회 기능을 사용할 수 있도록 기능분리하는 것이 목표이다.

 

 

 

 

3) 아예 ArithmetiCalculator 안에 메서드를 작성하지 않고 않고 함수형 인터페이스를 사용하는 방법

 

고민중에 함수형 인터페이스를 Predicate<T>의 형태로 정의해주지 않고 import를 통해서 바로 정의해줄 수 있다는 것도 알게되었다.

import java.util.function.Predicate;

 

 

방법은 매개변수로 Predicate<T>를 전달하면 된다. 전달하는 것은 람다로 정의한 함수 조건이다. 이렇게 하면 조건만 작성해서 매개변수로 넘기면 다양한 조건의 출력이 가능하다. 점을 활용해서 부등호를 사용하여 특정값 이상뿐 아닌 이하의 출력도 가능하게 있도록 확장해보았다. ( 구현: 특정 연산자를 가진 연산결과 출력, 특정 값이상/이하의 연산결과 출력 ) 

 

입력값 이상또는 이하연산결과 출력가능

 

 

 

 

10. 계산기 기능추가하기 (나머지, 제곱 연산)

앞서 enum열거형 자료에서 연산자와 연산 클래스를 매핑해주었다. 이를 통해 다른 연산자를 추가할 수 있을 것 같다고 생각했다.

중위 계산식을 사용하면서 2개의 피연산자가 필요한 연산 중 %(나머지),^(제곱)을 선택해보았다.

 

전위 계산식을 사용한다면 피연산자를 하나만 받아서 입력받을 수 있어 다른 고급 수학식 계산도 가능할 수 있을 것 같지만 꽤 수정이 필요할 것 같아 확장의 가능성만 보여주려고 한다.

 

switch 사용하지 않기 때문에 수정할 곳은 enum 연산클래스 두곳 뿐이다.

 

enum 추가

 

연산 클래스 추가

 

 

 

 

 

11. lookup에서 parser 빠져나오면 형관련 오류가 발생함(트러블슈팅)

class java.lang.Integer cannot be cast to class java.lang.Double (java.lang.Integer and java.lang.Double are in module java.base of loader 'bootstrap')

오류는 여기서 나는 것 같다. 해당 예외를 throw로 메세지를 지정해주지 않아서 그대로 예외메세지가 출력된다.

 

해당예외는 리턴된 것은 Integer형인데 Double로 받는다고 자료형의 차이에 대해 설명하고 있다. 

내가 작성한 double conditionValue = (double) parser.Numcheck(conditionAns[1])); 는 메서드에서 Integer가 반환될 경우 Integer을 double로 바꿀 수 없기 때문이다. 만약 int를 반환했다면 (double)을 사용해도 예외 발생없이 실행가능했을 것이다.

 

 

이 오류를 고치기 위해 Double.parseDouble()이나 Double.valueOf()를 사용해봤는데 이 메서드 둘다 문자열을 매개변수로 받고 하나씩 double, Double형으로 리턴하는 것이라 Integer를 매개변수로 받으니 코드 오류가 발생했다.

 

Parser.Numcheck(String)은 Number자료형을 반환한다. 그럼 Number에서 double로 바꾸는 메서드를 지원하지 않을까? 실제로 그런 메서드가 있어서 바로 적용해보았다.

 

Number클래스 .doubleValue()설명이다.

 

이를 활용하여 코드를 다음과 같이 수정해보았다.

double conditionValue = parser.NumCheck(conditionAns[1]).doubleValue();

 

 

수정 뒤 예외없이 실행

 

이렇게 코드를 수정하니 정상작동하는 것을 확인할 수 있었다.

 

 

 

 

 

다음과 같은 과정을 거쳐 이후 클래스를 자바 파일에 각각 두기, 주석달기, 문법 수정 등만 변경하여 계산기 lv.3까지 완성할 있었다.