[25/07/10] 계산기 과제 중간 정리

김기수·2025년 7월 10일

구현 단계

[1단계] : 클래스 없이 자바의 기본 문법만을 사용하여 구현한 계산기

[2단계] : 객체 지향 개념을 적용하여 클래스 정의, 연산을 수행하는 메소드 작성한 계산기

[3단계] : 자바의 Enum, 제네릭, 람다 & 스트림을 적용한 계산기

[1단계]

양의 정수(0 포함) 입력받기

  • Scanner를 사용해 양의 정수 2개를 전달받아 적합한 타입으로 선언한 변수에 저장했다.
package com.example.calculator;
import java.util.Scanner;

public class Calculator {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        System.out.println("첫 번째 숫자를 입력하세요: ");
        int n = sc.nextInt();
        System.out.println("두 번째 숫자를 입력하세요: ");
        int m = sc.nextInt();
    }
}

사칙연산 기호를 입력받기

  • Scanner을 사용해 사칙연산 기호를 전달 받고 적합한 타입의 변수에 저장했다.
package com.example.calculator;
import java.util.Scanner;

public class Calculator {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        System.out.println("첫 번째 숫자를 입력하세요: ");
        int n = sc.nextInt();
        System.out.println("두 번째 숫자를 입력하세요: ");
        int m = sc.nextInt();
        System.out.println("사칙연산 기호를 입력하세요: ");
        char c = sc.next().charAt(0);
    }
}

입력받은 정수와 사칙연산 기호를 사용하여 연산을 진행 후 결과 출력하기

  • Swich 제어문으로 사칙연산 기호에 맞는 연산자를 사용해 연산을 진행했다.
  • 연산 오류가 발생할 경우 해당 오류에 대한 내용을 출력하도록 했다.
package com.example.calculator;
import java.util.Scanner;

public class Calculator {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        System.out.println("첫 번째 숫자를 입력하세요: ");
        int n = sc.nextInt();
        System.out.println("두 번째 숫자를 입력하세요: ");
        int m = sc.nextInt();
        System.out.println("사칙연산 기호를 입력하세요: ");
        char c = sc.next().charAt(0);
        
		int result = 0;
        switch (c) {
            case '+':
                result = n + m;
                break;
            case '-':
                result = n - m;
                break;
            case '*':
                result = n * m;
                break;
            case '/':
                if (m == 0) {
            		System.out.println("나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다");
                    break;
                }
                result = n / m;
                break;
        	}
            
        	if (!(c == '/' && m == 0)) // 분모가 0인 나눗셈 처리
            System.out.println("결과: " + result);
	}
}

반복문을 사용해 무한으로 계산을 진행하되, "exit" 문자열을 입력하면 종료되게 하기

  • do-while 반복문을 사용해 무한으로 계산을 진행하고 "exit" 입력시 종료되게 했다.
package com.example.calculator;
import java.util.Scanner;

public class Calculator {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        do {
            System.out.println("첫 번째 숫자를 입력하세요: ");
            int n = sc.nextInt();
            System.out.println("두 번째 숫자를 입력하세요: ");
            int m = sc.nextInt();
            System.out.println("사칙연산 기호를 입력하세요: ");
            char c = sc.next().charAt(0);

            int result = 0;
            switch (c) {
                case '+':
                    result = n + m;
                    break;
                case '-':
                    result = n - m;
                    break;
                case '*':
                    result = n * m;
                    break;
                case '/':
                    if (m == 0) {
                        System.out.println("나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다");
                        break;
                    }
                    result = n / m;
                    break;
            }

            if (!(c == '/' && m == 0)) // 분모가 0인 나눗셈 처리
                System.out.println("결과: " + result);

            System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
        } while (!sc.next().equals("exit"));
    }
}

[2단계]

연산을 진행하는 Calulator 클래스와 main 메서드를 가지고있는 App 클래스 생성하기

  • main과 연산 부분을 클래스로 나눴다.
package com.example.calculator;
import java.util.ArrayList;
import java.util.List;

public class Calculator {
    // 연산 결과 저장
    List<Integer> results = new ArrayList<Integer>();

    public int calculate(int n, int m, char c) {
        int result = 0;
        switch (c) {
            case '+':
                result = n + m;
                break;
            case '-':
                result = n - m;
                break;
            case '*':
                result = n * m;
                break;
            case '/':
                if (m == 0) {
                    System.out.println("나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다");
                    return 0;
                }
                result = n / m;
                break;
        }

        results.add(result);
        return result;
    }
}
package com.example.calculator;
import java.util.Scanner;

public class App {
    public static void main(String[] args) {
        Calculator calculator = new Calculator();
        Scanner sc = new Scanner(System.in);

        do {
            System.out.println("첫 번째 숫자를 입력하세요: ");
            int n = sc.nextInt();
            System.out.println("두 번째 숫자를 입력하세요: ");
            int m = sc.nextInt();
            System.out.println("사칙연산 기호를 입력하세요: ");
            char c = sc.next().charAt(0);
            int result = calculator.calculate(n, m, c);
            if (!(c == '/' && m == 0))
                System.out.println("결과: " + result);

            System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
        }
        while (!sc.next().equals("exit"));
    }
}

다른 클래스에서 Calculator 클래스의 연산 결과를 저장하고 있는 컬렉션 필드에 직접 접근하지 못하게 하기(캡슐화)

  • Getter Setter를 구현하고 컬렉션 필드에 private를 걸었다.
package com.example.calculator;
import java.util.ArrayList;
import java.util.List;

public class Calculator {
    // 연산 결과 저장
    private List<Integer> results = new ArrayList<Integer>();

    public List<Integer> getResults() {
        return results;
    }

    public void setResults(List<Integer> results) {
        this.results = results;
    }

    public int calculate(int n, int m, char c) {
        int result = 0;
        switch (c) {
            case '+':
                result = n + m;
                break;
            case '-':
                result = n - m;
                break;
            case '*':
                result = n * m;
                break;
            case '/':
                if (m == 0) {
                    System.out.println("나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다");
                    return 0;
                }
                result = n / m;
                break;
        }

        results.add(result);
        return result;
    }
}

Calculator 클래스에 저장된 연산 결과 중 가장 먼저 저장된 데이터를 삭제하는 기능을 추가하고 App 클래스의 main 메서드에 삭제 메서드가 활용될 수 있도록 수정하기

  • Calculator 클래스에 removeResult 함수를 추가하고 App 클래스 main에서 호출시켜 결과 값을 출력하게 했다.
package com.example.calculator;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class App {
    public static void main(String[] args) {
        Calculator calculator = new Calculator();
        Scanner sc = new Scanner(System.in);

        do {
            System.out.println("첫 번째 숫자를 입력하세요: ");
            int n = sc.nextInt();
            System.out.println("두 번째 숫자를 입력하세요: ");
            int m = sc.nextInt();
            System.out.println("사칙연산 기호를 입력하세요: ");
            char c = sc.next().charAt(0);
            int result = calculator.calculate(n, m, c);
            if (!(c == '/' && m == 0))
                System.out.println("결과: " + result);

            System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
        }
        while (!sc.next().equals("exit"));

        System.out.println("저장 된 결과값: " + calculator.getResults());
        List<Integer> results = calculator.getResults();
        results.add(1000);
        calculator.setResults(results);
        System.out.println("1000을 넣고 변경 된 결과값: " + calculator.getResults());
        calculator.removeResult();
        System.out.println("가장 먼저 저장된 데이터 삭제 후 변경 된 결과값: " + calculator.getResults());
    }
}
package com.example.calculator;
import java.util.ArrayList;
import java.util.List;

public class Calculator {
    // 연산 결과 저장
    private List<Integer> results = new ArrayList<Integer>();

    public List<Integer> getResults() {
        return results;
    }

    public void setResults(List<Integer> results) {
        this.results = results;
    }

    public int calculate(int n, int m, char c) {
        int result = 0;
        switch (c) {
            case '+':
                result = n + m;
                break;
            case '-':
                result = n - m;
                break;
            case '*':
                result = n * m;
                break;
            case '/':
                if (m == 0) {
                    System.out.println("나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다");
                    return 0;
                }
                result = n / m;
                break;
        }

        results.add(result);

        return result;
    }
	//가장 먼저 저장된 결과값 제거
    public void removeResult() {
        results.remove(0);
    }
}

Scanner 예외 처리하기

  • 스캐너에 래핑을 해서 올바른 값이 입력될 때 까지 반복하게 만들었다.
package com.example.calculator;
import java.util.InputMismatchException;
import java.util.List;
import java.util.Scanner;

public class App {
    public static void main(String[] args) {
        ArithmeticCalculator calculator = new ArithmeticCalculator();
        Scanner sc = new Scanner(System.in);

        do {
            System.out.println("첫 번째 숫자를 입력하세요: ");
            int n = safeNextInt(sc);
            System.out.println("두 번째 숫자를 입력하세요: ");
            int m = safeNextInt(sc);
            System.out.println("사칙연산 기호를 입력하세요: ");
            char c = safeNextChar(sc);

            try {
                int result = calculator.calculate(n, m, c);
                System.out.println("결과: " + result);
            } catch (ArithmeticException e) {
                System.out.println("나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다");
            }
            System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
        }
        while (!sc.next().equals("exit"));

        System.out.println("연산 된 결과값들: " + calculator.getResults());
        List<Integer> results = calculator.getResults();
        results.add(1000);
        calculator.setResults(results);
        System.out.println("1000을 넣고 변경 된 결과값: " + calculator.getResults());
        calculator.removeResult();
        System.out.println("가장 먼저 저장된 데이터 삭제 후 변경 된 결과값: " + calculator.getResults());
    }
	
    public static int safeNextInt(Scanner sc) {
        while (true) {
            try {
                return sc.nextInt();
            } catch (InputMismatchException e) {
                System.out.println("[Error] 숫자를 입력해 주세요.");
                sc.nextLine();
            }
        }
    }

    public static char safeNextChar(Scanner sc) {
        while (true) {
            try {
                char c = sc.next().charAt(0);
                if (c == '+' || c == '-' || c == '*' || c == '/')
                    return c;
                else {
                    System.out.println("[Error] 올바른 사칙연산 기호를 입력해 주세요.");
                    sc.nextLine();
                }
            } catch (InputMismatchException e) {
                System.out.println("[Error] 올바른 사칙연산 기호를 입력해 주세요.");
                sc.nextLine();
            }
        }
    }
}

[3단계]

사칙연산 기호를 Enum 타입으로 관리하고 ArithmeticCalculator 클래스에 활용하기

  • App 클래스에는 사칙연산을 받던 char 타입의 변수를 Enum 타입인 OperatorType 변수로 바꿧고,
    옳바른 값이 입력되도록 처리하는 safeNextOperator 함수에서 OperatorType.getType() 함수의 예외를 받아 처리한다.
package com.example.calculator;
import java.util.InputMismatchException;
import java.util.List;
import java.util.Scanner;

public class App {
    public static void main(String[] args) {
        ArithmeticCalculator calculator = new ArithmeticCalculator();
        Scanner sc = new Scanner(System.in);

        do {
            System.out.println("첫 번째 숫자를 입력하세요: ");
            int n = safeNextInt(sc);
            System.out.println("두 번째 숫자를 입력하세요: ");
            int m = safeNextInt(sc);
            System.out.println("사칙연산 기호를 입력하세요: ");
            OperatorType c = safeNextOperator(sc);

            try {
                int result = calculator.calculate(n, m, c);
                System.out.println("결과: " + result);
            } catch (ArithmeticException e) {
                System.out.println("나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다");
            }
            System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
        }
        while (!sc.next().equals("exit"));

        System.out.println("연산 된 결과값들: " + calculator.getResults());
        List<Integer> results = calculator.getResults();
        results.add(1000);
        calculator.setResults(results);
        System.out.println("1000을 넣고 변경 된 결과값: " + calculator.getResults());
        calculator.removeResult();
        System.out.println("가장 먼저 저장된 데이터 삭제 후 변경 된 결과값: " + calculator.getResults());
    }

    public static int safeNextInt(Scanner sc) {
        while (true) {
            try {
                return sc.nextInt();
            } catch (InputMismatchException e) {
                System.out.println("[Error] 숫자를 입력해 주세요.");
                sc.nextLine();
            }
        }
    }

    public static OperatorType safeNextOperator(Scanner sc) {
        while (true) {
            try {
                char c = sc.next().charAt(0);
                return OperatorType.getType(c);
            } catch (Exception e) {
                System.out.println("[Error] 올바른 사칙연산 기호를 입력해 주세요.");
                sc.nextLine();
            }
        }
    }
}
  • OperatorType 클래스는 사칙연산 기호를 관리하고 getType() 함수를 통해 올바른 사칙연산 기호의 타입만 출력될 수 있도록 했다.
package com.example.calculator;

public enum OperatorType {
    SUM('+'),
    SUB('-'),
    MUL('*'),
    DIV('/');

    private final char symbol;

    OperatorType(char c) {
        this.symbol = c;
    }

    public char getSymbol() {
        return symbol;
    }

    public static OperatorType getType(char c) {
        for (OperatorType type : values()) {
            if (type.symbol == c) {
                return type;
            }
        }
        throw new IllegalArgumentException("[Error] 올바른 사칙연산 기호를 입력해 주세요.: " + c);
    }
}
  • ArithmeticCalculator 클래스는 calulate 메서드가 기존 char 형식의 사칙연산 기호를 받은 것을 OperatorType으로 변경했고, switch 문의 조건을 char 대신 OperatorType 타입을 검사하도록 대체했다.
package com.example.calculator;
import java.util.ArrayList;
import java.util.List;

public class ArithmeticCalculator {
    // 연산 결과 저장
    private List<Integer> results = new ArrayList<Integer>();

    public List<Integer> getResults() {
        return results;
    }

    public void setResults(List<Integer> results) {
        this.results = results;
    }

    public int calculate(int n, int m, OperatorType o) throws ArithmeticException {
        int result = 0;
        switch (o) {
            case SUM:
                result = n + m;
                break;
            case SUB:
                result = n - m;
                break;
            case MUL:
                result = n * m;
                break;
            case DIV:
                try {
                    result = n / m;
                } catch (ArithmeticException e) {
                    throw e;
                }
                break;
        }
        results.add(result);
        return result;
    }

    public void removeResult() {
        results.remove(0);
    }
}

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

  • App 클래스는 safeNextNumber 함수에서 입력을 String으로 받아 Integer, Double 타입으로 분류한 뒤 Number타입으로 업캐스팅해 반환해줬다.
package com.example.calculator;
import java.util.InputMismatchException;
import java.util.List;
import java.util.Scanner;

public class App {
    public static void main(String[] args) {
        ArithmeticCalculator<Number> calculator = new ArithmeticCalculator<>();
        Scanner sc = new Scanner(System.in);

        do {
            System.out.println("첫 번째 숫자를 입력하세요: ");
            Number n = safeNextNumber(sc);
            System.out.println("두 번째 숫자를 입력하세요: ");
            Number m = safeNextNumber(sc);
            System.out.println("사칙연산 기호를 입력하세요: ");
            OperatorType c = safeNextOperator(sc);

            try {
                Number result = calculator.calculate(n, m, c);
                System.out.println("결과: " + result);
            } catch (ArithmeticException e) {
                System.out.println("나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다");
            }
            System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
        }
        while (!sc.next().equals("exit"));

        System.out.println("연산 된 결과값들: " + calculator.getResults());
        List<Number> results = calculator.getResults();
        results.add(1000);
        calculator.setResults(results);
        System.out.println("1000을 넣고 변경 된 결과값: " + calculator.getResults());
        calculator.removeResult();
        System.out.println("가장 먼저 저장된 데이터 삭제 후 변경 된 결과값: " + calculator.getResults());
    }

    public static Number safeNextNumber(Scanner sc) {
        while (true) {
            String input = sc.next();
            if (isInteger(input)) {
                return Integer.parseInt(input);
            } else if (isDouble(input)) {
                return Double.parseDouble(input);
            }
            System.out.println("[Error] 숫자를 입력해 주세요.");
            sc.nextLine();
        }
    }

    static boolean isInteger(String s) {
        try {
            Integer.parseInt(s);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    static boolean isDouble(String s) {
        try {
            Double.parseDouble(s);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    public static OperatorType safeNextOperator(Scanner sc) {
        while (true) {
            try {
                char c = sc.next().charAt(0);
                return OperatorType.getType(c);
            } catch (Exception e) {
                System.out.println("[Error] 올바른 사칙연산 기호를 입력해 주세요.");
                sc.nextLine();
            }
        }
    }
}
  • ArithmeticCalculator 클래스는 제네릭을 추가시키고, calculate 함수에 제네릭 매개변수를 받아 실수화한 뒤 연산을 하고, 결과값은 소숫점 유무에 따라 Integer, Double 타입을 Number 타입으로 업캐스팅해 반환했다.
package com.example.calculator;
import java.util.ArrayList;
import java.util.List;

public class ArithmeticCalculator<T extends Number> {
    // 연산 결과 저장
    private static List<Number> results = new ArrayList<>();

    public List<Number> getResults() {
        return results;
    }

    public void setResults(List<Number> results) {
        this.results = results;
    }

    public Number calculate(T n, T m, OperatorType o) throws ArithmeticException {
        double result = calculate(n.doubleValue(), m.doubleValue(), o);

        if (result % 1 == 0) {
            results.add((int) result);
            return (int) result;
        }
        results.add(result);
        return result;

    }

    double calculate(double n, double m, OperatorType o) throws ArithmeticException {
        return switch (o) {
            case SUM -> n + m;
            case SUB -> n - m;
            case MUL -> n * m;
            case DIV -> {
                if (m == 0) throw new ArithmeticException("0으로 나눌 수 없습니다.");
                yield n / m;
            }
        };
    }

    public void removeResult() {
        results.remove(0);
    }

}
  • 구현하면서 생긴 의문점
  1. scanner.next() 로 정수나 실수를 받을텐데 이를 parse 해서 타입을 분리한 뒤 calculate() 함수에 매개변수로 넣어야 되나?
String input = scanner.next();
Number num;
if(isInteger(input)) 
	num = Integer.parseInt(input));
else if(isDouble)
	num = Double.parseInt(input));
  1. ArithmeticCalculator 클래스에 제네릭을 사용하면 App클래스 내부에 사용시 입력받은 타입에 따라 제네릭을 부여해야 되나?
ArithmeticCalculator<Double> doubleCal;
ArithmeticCalculator<Integer> intCal;
  1. caculate() 함수 내부에서 제네릭 타입 T끼리는 직접적인 연산이 안되는데 제네릭으로 구현하는 의미가 없는거 아닐까?

  2. 제네릭 타입끼리 연산을 위해 T 타입을 Number로 상속하고 실수로 변환하는 과정을 사용할 수 밖에 없는데,
    제네릭대신 매개변수를 Number로 받아도 똑같이 동작하면 제네릭을 쓰는 의미가 없지 않을까?

  3. 입력 타입은 숫자로 고정 돼있는데 제네릭을 사용함으로 과도하게 추상화 된게 아닐까?

저장된 연산 결과들 중 Scanner로 입력받은 값보다 큰 결과값들을 출력

  • App 클래스는 main() 함수에서 계산기가 "exit"로 종료된 후,
    추가 입력을 받아 람다와 스트림을 사용해 해당 값보다 큰 수를 조회하고,
    그 값을 출력하도록 구현했다.
package com.example.calculator;

import java.util.List;
import java.util.Scanner;

public class App {
    public static void main(String[] args) {
        ArithmeticCalculator<Number> calculator = new ArithmeticCalculator<>();
        Scanner sc = new Scanner(System.in);

        do {
            System.out.println("첫 번째 숫자를 입력하세요: ");
            Number n = safeNextNumber(sc);
            System.out.println("두 번째 숫자를 입력하세요: ");
            Number m = safeNextNumber(sc);
            System.out.println("사칙연산 기호를 입력하세요: ");
            OperatorType c = safeNextOperator(sc);

            try {
                Number result = calculator.calculate(n, m, c);
                System.out.println("결과: " + result);
            } catch (ArithmeticException e) {
                System.out.println("나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다");
            }
            System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
        }
        while (!sc.next().equals("exit"));

        System.out.println("연산 된 결과값들: " + calculator.getResults());
        System.out.println("입력된 수 보다 큰 수 조회: ");
        Number input = safeNextNumber(sc);
        List<Number> filter = calculator.getResults().stream().filter(num -> num.doubleValue() > input.doubleValue()).toList();
        System.out.println(input + " 보다 큰 수\n" + filter);
        List<Number> results = calculator.getResults();
        results.add(1000);
        calculator.setResults(results);
        System.out.println("1000을 넣고 변경 된 결과값: " + calculator.getResults());
        calculator.removeResult();
        System.out.println("가장 먼저 저장된 데이터 삭제 후 변경 된 결과값: " + calculator.getResults());

    }

    public static Number safeNextNumber(Scanner sc) {
        while (true) {
            String input = sc.next();
            if (isInteger(input)) {
                return Integer.parseInt(input);
            } else if (isDouble(input)) {
                return Double.parseDouble(input);
            }
            System.out.println("[Error] 숫자를 입력해 주세요.");
            sc.nextLine();
        }
    }

    static boolean isInteger(String s) {
        try {
            Integer.parseInt(s);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    static boolean isDouble(String s) {
        try {
            Double.parseDouble(s);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    public static OperatorType safeNextOperator(Scanner sc) {
        while (true) {
            try {
                char c = sc.next().charAt(0);
                return OperatorType.getType(c);
            } catch (Exception e) {
                System.out.println("[Error] 올바른 사칙연산 기호를 입력해 주세요.");
                sc.nextLine();
            }
        }
    }
}
profile
백엔드 개발자

0개의 댓글