반응형
1. 오늘 학습 키워드 : 계산기 3단계 도전!!
2. TMI
아침에 일정으로 인해서 점심시간쯤에 출석하였다.
3. 학습내용
■ 계산기 과제 3단계 ■
| Enum, 제네릭, 람다 & 스트림을 이해한 계산기 만들기 |
3단계는 자주 사용하지 않은 문법들이 많아서, gpt와 튜터님들의 도움을 많이 받았다.
package com.example.calculator;
import java.util.Scanner;
public class App {
public static void main(String[] args) {
// 3. Enum, 제네릭, 람다 & 스트림을 이해한 계산기 만들기
Scanner sc = new Scanner(System.in);
ArithmeticCalculator <Number> calculator = new ArithmeticCalculator<>();
while(true) {
System.out.print("첫번째 수를 입력하세요(실수 O): ");
double num1 = sc.nextDouble();
if (num1 < 0) {
System.out.println("음수는 입력할수 없습니다.");
continue;
}
System.out.print("두번째 정수를 입력하세요(실수 0): ");
double num2 = sc.nextDouble();
if (num2 < 0) {
System.out.println("음수는 입력할수 없습니다.");
continue;
}
System.out.print("사칙연산(+, -, x, /) 기호를 입력하세요: ");
char op = sc.next().charAt(0); // 문자열에서 첫 번째 문자만 추출
try {
double result = calculator.calculate(num1, num2, OperatorType.fromChar(op));
System.out.println("결과 = " + result);
} catch (Exception e) {
System.out.println(e.getMessage());
}
System.out.print("결과 필터링 값 입력 (입력값보다 큰 결과 조회, -1 입력 시 건너뜀): ");
double filterValue = sc.nextDouble();
if (filterValue >= 0) {
System.out.println("필터링된 결과: " + calculator.filterResultsGreaterThan(filterValue));
}
sc.nextLine(); // 버퍼 지우기
System.out.print("더 계산하시겠습니까? (exit 입력 시 종료): ");
String answer = sc.nextLine();
if (answer.equals("exit")){
System.out.println("계산기 시스템을 종료합니다.");
System.out.println("최종연산 결과 기록 : " + calculator.getRecord());
break;
}
System.out.print("먼저 저장된 연산 결과 기록을 삭제 하겠습니까? (0 입력시 삭제): ");
String delete = sc.nextLine();
if (delete.equals("0")) {
calculator.removeResult();
System.out.println("가장 먼저 들어온 연산 결과가 삭제 되었습니다.");
}
// 저장된 연산 결과값 매회 조회.
System.out.println(calculator.getRecord());
}
}
}
Main 클래스
package com.example.calculator;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class ArithmeticCalculator <T extends Number> {
private List<Double> record = new ArrayList<>();
public double calculate(T num1, T num2, OperatorType op) {
double result = op.apply(num1,num2);
record.add(result); // 연산 결과 기록 추가함.
return result;
}
public void removeResult() {
if(!record.isEmpty()) {
record.remove(0); // 가장 먼저 저장된 값을 삭제함.
} else {
System.out.println("연산 결과가 기록되어 있지 않습니다.");
}
}
public List<Double> filterResultsGreaterThan(T value) {
return record.stream()
.filter(result -> result > value.doubleValue())
.collect(Collectors.toList());
}
public void setRecord(List<Double> record) {
this.record = record;
}
public List<Double> getRecord() {
return record;
}
}
ArithmeticCalculator 클래스
package com.example.calculator;
public enum OperatorType {
// (double) a 는 형변환, a.doubleValue()는 같은 의미임 doubleValue는 Number의 객체.
ADD('+') {
@Override
public <T extends Number> double apply(T a, T b) {
return a.doubleValue() + b.doubleValue();
}
},
SUB('-') {
@Override
public <T extends Number> double apply(T a, T b) {
return a.doubleValue() - b.doubleValue();
}
},
MUL('*') {
@Override
public <T extends Number> double apply(T a, T b) {
return (double) a * (double) b;
}
},
DIV('/') {
@Override
public <T extends Number> double apply(T a, T b) {
if (b.doubleValue() == 0) {
throw new ArithmeticException("나눗셈 연산에서 분모에 0이 입력될 수 없습니다.");
}
return (double) a / (double) b;
}
};
// 속성 [ enum 타입은 그 위에 enum타입용 메소드가 먼저 와야하는 특성이 있다? ]
private final char op;
public abstract <T extends Number> double apply(T a, T b);
// 생성자
OperatorType(char op) {
this.op = op;
}
public static OperatorType fromChar(char op) {
return switch (op) {
case '+' -> ADD; // -> 는 switch - case 문에서 break; 없이 편하게 개발할수 있음. ( 가독성 ↑ )
case '-' -> SUB; // : 처럼 일반적인 switch - case문과는 다르다.
case '*' -> MUL;
case '/' -> DIV;
default -> throw new IllegalStateException("부호를 잘못 입력하였습니다");
};
}
}
OperatorType (Enum타입)
[트러블 슈팅]
- 배경
Enum을 사용하여 연산자(+, -, *, /)를 정의하고 이를 계산 로직에 적용하려 했지만, 연산자와 관련된 코드에서 여러 가지 문제가 발생했다. 주로 Enum 상수의 선언 순서 문제와, switch 문에서 새로운 문법(->)을 사용하는 문제, 숫자 타입을 범용적으로 처리하기 위한 코드 확장 문제였다. - 전개
- Enum 타입에서 상수(ADD, SUBTRACT 등)가 먼저 선언되어야 한다는 점을 깨달았다. Java의 Enum은 객체를 미리 생성하는 방식이므로, Enum 상수보다 필드나 메서드가 먼저 나오면 안 된다는 사실을 알게 되었다.
- switch 문에서는 Java 14에서 추가된 새로운 문법인 -> 연산자가 사용되었는데, 이는 Java 14 이상에서만 동작하므로, 하위 버전에서는 기존 문법을 사용해야 한다는 점을 인식하게 되었다.
- 숫자 타입을 범용적으로 처리하기 위해 int 타입에서 double, float 등의 타입을 지원하도록 코드 확장이 필요하다는 사실을 깨달았다. 이를 위해 제네릭을 사용하여 Number 타입을 처리할 수 있도록 변경해야 했다.
- 위기
- Enum 선언 순서를 잘못 지키면 오류가 발생하고, 필드와 메서드가 상수보다 먼저 나오지 않도록 해야 한다는 점을 인지했다.
- Java 14 이하 버전에서 -> 연산자가 동작하지 않기 때문에, 코드 버전 호환성 문제를 해결해야 한다는 점을 알게 되었다.
- 단순히 int를 double로 바꾸는 것만으로는 범용적인 계산기를 구현할 수 없으며, 제네릭을 활용해 다양한 숫자 타입을 지원해야 한다는 점이 문제였다.
- 발단
처음에는 Enum 내에 필드(symbol)와 메서드(apply 등)를 Enum 상수들보다 먼저 선언했더니 컴파일 오류가 발생했다. 또한, switch 문에서 기존의 : 대신 -> 연산자가 사용되었고, 숫자 타입을 int로 고정하여 double 연산을 할 수 없는 구조가 문제로 지적되었다. - 절정
- Enum 상수 먼저 선언: Enum 상수는 가장 먼저 선언되어야 하며, 필드 및 메서드는 그 후에 선언되도록 순서를 수정했다. 이를 통해 컴파일 오류를 해결했다.
- Switch 문법 개선: Java 14 이상에서는 -> 연산자를 사용하여 switch 문을 간결하게 작성할 수 있지만, 하위 버전에서는 기존 문법을 사용해야 한다는 점을 고려하여 코드 호환성 문제를 해결했다.
- 범용적인 계산기 구조 개선: 기존의 int 타입을 고정한 코드를 제네릭을 활용하여 Number 타입을 지원하도록 개선했다. 이를 통해 int, double, float 등 다양한 숫자 타입을 처리할 수 있도록 했다.
- 결말
이제 Enum의 상수 순서 문제를 해결하고, 최신 switch 문법을 활용하여 코드의 가독성을 높였으며, 제네릭을 활용해 숫자 타입에 관계없이 범용적으로 동작하는 계산기 구조를 완성했다. 이를 통해 유지보수와 확장이 용이해졌으며, 다양한 숫자 타입을 처리할 수 있는 유연한 시스템이 구축되었다.
- 새롭게 알게된점
■ enum 타입 [ 상수를 여러개 선언할때 유리함 , 박성원 튜터님 튜터링 ]
원래 class 타입에서는 내에서 속성 / 생성자 / 기능 으로 분류되는데, enum 타입은 속성부분에서 또 다시 속성, 생성자, 기능 으로 분류 된다고 보면 편하다.
※ 원래 속성 기능이 속성 안의 속성,생성자,기능보다 위에 선언되어 있으면 오류가 발생한다. ( enum 특성 )
public enum OperatorType {
// (double) a 는 형변환, a.doubleValue()는 같은 의미임 doubleValue는 Number의 객체.
ADD('+') { // ADD [속성] , ( ) <-- 안의 내용 이 [생성자]
@Override // { } 구현체 안이 [기능]
public <T extends Number> double apply(T a, T b) {
return a.doubleValue() + b.doubleValue();
}
},
SUB('-') {
@Override
public <T extends Number> double apply(T a, T b) {
return a.doubleValue() - b.doubleValue();
}
},
MUL('*') {
@Override
public <T extends Number> double apply(T a, T b) {
return (double) a * (double) b;
}
},
DIV('/') {
@Override
public <T extends Number> double apply(T a, T b) {
if (b.doubleValue() == 0) {
throw new ArithmeticException("나눗셈 연산에서 분모에 0이 입력될 수 없습니다.");
}
return (double) a / (double) b;
}
};
// 속성 [enum 타입은 그 위에 enum타입용 메소드가 먼저 와야하는 특성이 있다? ]
private final char op;
public abstract <T extends Number> double apply(T a, T b);
// 생성자
OperatorType(char op) {
this.op = op;
}
public static OperatorType fromChar(char op) {
return switch (op) {
case '+' -> ADD; // -> 는 switch - case 문에서 break; 없이 편하게 개발할수 있음.
case '-' -> SUB; // : 처럼 일반적인 switch - case문과는 다르다.
case '*' -> MUL;
case '/' -> DIV;
default -> throw new IllegalStateException("부호를 잘못 입력하였습니다");
};
}
}
★ Switch - case 문
| 문법 방식 | 사용 가능 Java 버전 | break 필요 여부 | 가독성 |
| : (기존 switch) | Java 11 이하 | 필요 | 보통 |
| -> (Switch Expressions) | Java 12 이상 | 불필요 | 좋음 |
4. 내일 학습 할 것: 계산기 과제 + 자바 복습
반응형
'[내일배움캠프-Sparta] > Spring 6기' 카테고리의 다른 글
| TIL 14 [ 키오스크 과제(1~2단계) ] (0) | 2025.03.07 |
|---|---|
| TIL 13 [ Java(동일성 문제, 클래스와 객체, charAt함수) ] (0) | 2025.03.06 |
| TIL 11 [Java(접근제어자, 클래스란?, 객체지향?) + 계산기 과제 2단계 ] (0) | 2025.03.04 |
| TIL 10 [ Java(람다식, 스트림, 쓰레드) + 계산기 과제 1단계 ] (0) | 2025.02.28 |
| TIL 9 [ Java(예외처리, Optional, 제네릭, 컬렉션) ] (0) | 2025.02.27 |