입출력
멘토님 코드
- 음수 입력시에 재입력을 받도록 처리가 되어 있음
if (firstNumber < 0) {
System.out.println("음수는 입력할 수 없습니다. 프로그램을 재시작합니다.");
continue;
}
세이프티 코딩 (Safety Coding) 적용
- 문자열 비교 시 변수에 담지 않고, 해당 문자열 자체로 equals를 사용하여 비교함 -> 세이프티 코딩 (Safety Coding)
- 널은 메서드를 사용하지 못하기 때문에 스트링으로 비교하면 프로그램이 종료됨
- NullPointerException 방지
if ("exit".equals(sc.next())) {
break;
}
기존에 내가 작성한 코드
- 변수명에 널이 들어갈 수 있기 때문에 위험함
String exit = sc.nextLine();
exit = exit.toLowerCase();
if (exit.equals("exit")){
break;
}
입력방식
- sc.next() : 공백을 포함하지 않는 단일 토큰 입력시 사용, 공백 기준으로 공백 앞에 있는 문자열만 받아옴
- sc.nextLine() : 엔터를 기준으로 한 줄 전체를 입력받음
List return시에 toString으로 변환하지 않고, 그냥 return 해도 잘 반환 -> 편의를 위해서 toString이 내부적으로 구현되어 있음
객체지향 활용
클래스 내부에 필드는 final로 선언하기
- 컬랙션에 대해서 여러 동작을 수행해도 스택에 저장되어 있는 인스턴스의 주소값을 변경하는 것이 아니기 때문에 변경해도 됨
private final List<Double> resultList = new ArrayList<>();
기존에 내가 작성한 코드
- final을 붙이지 않음
private List<Integer> results = new ArrayList<Integer>();
컬랙션 선언시, 타입은 반드시 인터페이스 타입으로 선언하기
- List 인터페이스를 구현한 것이 ArrayList이기 때문에, 타입은 상위 인터페이스 타입으로 지정하기
- 나중에 ArrayList -> LinkedList로 변경 할 수 있음 즉, 구현체를 나중에 쉽게 변경할 수 있으므로 재사용성과 유지보수성 용이(다형성)
private final List<Double> resultList = new ArrayList<>();
Enum class의 활용
- Calculator클래스의 계산 메서드를 실행할 때 연산자 타입을 선언해둔 enum 클래스를 활용하여 전달
- enum클래스에서 입력받은 Sting타입의 연산자와 비교하여 변환하는 메서드는 static으로 선언
public enum Operator {
ADD("+"),
SUBTRACT("-"),
MULTIPLY("*"),
DIVIDE("/");
private final String symbol;
Operator(String symbol) {
this.symbol = symbol;
}
public String getSymbol() {
return symbol;
}
// 입력받은 문자열과 매칭되는 클래스인지 확인하는 정적 메서드
public static Operator fromSymbol(String symbol) {
for (Operator operator : values()) {
// 매칭되는 해당 문자열에 해당하는 클래스 반환
if (operator.getSymbol().equals(symbol)) {
return operator;
}
}
throw new IllegalArgumentException("유효하지 않은 연산 기호입니다. (+, -, *, / 중 하나여야 합니다)");
}
}
String symbol = sc.next();
// 입력받은 String을 이넘 타입에 넣어서 매칭되는 타입으로 반환
Operator operator = Operator.fromSymbol(symbol);
//이넘 타입으로 넘겨줌
System.out.println("결과: " + calculator.calculate(firstNumber, secondNumber, operator));
기존에 내가 작성한 코드
- 연산 메서드로 전달할 때 타입은 그냥 String으로 넘기고, 메서드만 오버라이딩 해서 사용함
- 결국 연산자 자체를 String으로 사용해서 비교했기에 enum 클래스를 연산자 이름 측면에서는 잘 활용 못함
public enum OperatorType {
PLUS("+") {
@Override
public <T extends Number> double calculate(T firstInput, T secondInput) {
return firstInput.doubleValue() + secondInput.doubleValue();
}
},
MINUS("-") {
@Override
public <T extends Number> double calculate(T firstInput, T secondInput) {
return firstInput.doubleValue() - secondInput.doubleValue();
}
},
MULTIPLY("*") {
@Override
public <T extends Number> double calculate(T firstInput, T secondInput) {
return firstInput.doubleValue() * secondInput.doubleValue();
}
},
DIVIDE("/") {
@Override
public <T extends Number> double calculate(T firstInput, T secondInput) throws DivideByZeroException {
if (secondInput.doubleValue() == 0) {
throw new DivideByZeroException();
}
return firstInput.doubleValue() / secondInput.doubleValue();
}
};
private final String operator;
OperatorType(String operator) {
this.operator = operator;
}
//계산을 수행하는 메서드를 추상메서드로 선언하여 각각의 연산자 객체가 오버라이딩하여 구현
public abstract <T extends Number> double calculate(T firstInput, T secondInput) throws DivideByZeroException;
}
public <T extends Number> Double calculate(T firstInput, T secondInput, String operator) throws DivideByZeroException {
double answer = switch (operator) {
case "+" -> OperatorType.PLUS.calculate(firstInput, secondInput);
case "-" -> OperatorType.MINUS.calculate(firstInput, secondInput);
case "*" -> OperatorType.MULTIPLY.calculate(firstInput, secondInput);
case "/" -> OperatorType.DIVIDE.calculate(firstInput, secondInput);
default -> throw new IllegalArgumentException("Invalid operator: " + operator);
};
results.add(answer);
return answer;
}
제네릭 활용
- 계산기 클래스 범위에서 Number 하위 객체만 가능하도록 타입 제한
- Class<T> type; 필드를 생성자 주입이용하여 기록함 -> 런타임에 처음 생성했던 제네릭 타입은 사라지기 때문
- List<T> resultList에 값을 넣어줄 때 T자체로 넣어줌
public class ArithmeticCalculator<T extends Number> {
// 필드들의 타입들은 Number의 하위 타입
private final List<T> resultList = new ArrayList<>();
// 생성자 주입시 넣어준 타입
private final Class<T> type;
public ArithmeticCalculator(Class<T> type) {
this.type = type;
}
public T calculate(T firstNumber, T secondNumber, Operator operator) {
double num1 = firstNumber.doubleValue();
double num2 = secondNumber.doubleValue();
double result;
// 계산로직
T typedResult = castToType(result, type);
addResult(typedResult);
return typedResult;
}
- 처음에 생성한 타입에 맞게 계산 결과값을 변환시켜주는 메서드를 만들어 변환해줌
public static <T extends Number> T castToType(double result, Class<T> type) {
//type.getSimpleName() -> 클래스 명을 조회하는 메서드
return switch (type.getSimpleName()) {
case "Integer" -> type.cast((int) result);
case "Double" -> type.cast(result);
case "Long" -> type.cast((long) result);
default -> throw new IllegalArgumentException("지원하지 않는 타입입니다.");
};
}
기존에 내가 작성한 코드
- 입력받는 피연산자의 값이 어떤 숫자타입이든 상관없이 입력받는 부분에 초점을 맞춤
- 계산 결과값을 저장하는 컬렉션 타입을 Double로 지정하고 시작했기에 계산 결과값 저장부분에서 사용했다고 하기엔 아쉬움이 있음
- Double 지정 리스트 => double로 넣음
public Double executeCalculator() throws DivideByZeroException {
// 여기서 String 타입의 피연산자들을 실수형이나 정수형으로 변환하여 자유롭게 넣어 줄 수 있음, Integer도 가능
Double firstNumber = Double.parseDouble(firstInput);
Double secondNumber = Double.parseDouble(secondInput);
return arithmeticCalculator.calculate(firstNumber, secondNumber, operator);
}
public class ArithmeticCalculator {
private List<Double> results = new ArrayList<>();
public <T extends Number> Double calculate(T firstInput, T secondInput, String operator) throws DivideByZeroException {
double answer = switch (operator) {
case "+" -> OperatorType.PLUS.calculate(firstInput, secondInput);
case "-" -> OperatorType.MINUS.calculate(firstInput, secondInput);
case "*" -> OperatorType.MULTIPLY.calculate(firstInput, secondInput);
case "/" -> OperatorType.DIVIDE.calculate(firstInput, secondInput);
default -> throw new IllegalArgumentException("Invalid operator: " + operator);
};
results.add(answer);
return answer;
}
}
public enum OperatorType {
PLUS("+") {
@Override
public <T extends Number> double calculate(T firstInput, T secondInput) {
return firstInput.doubleValue() + secondInput.doubleValue();
}
},
}
멘토님 코드를 보면서 이넘, 제네릭에 대해서 확실하게 이해할 수 있었던 것 같다 😳
특히 제네릭은 런타임에 한번 지정하고 사라지기 때문에 따로 저장해야 하는 부분이 가장 인상적이었다
다음 과제는 이넘과 제네릭을 좀 더 잘 활용해보고 싶다
'JAVA' 카테고리의 다른 글
제네릭 타입제한과 필드에서의 값 전달 (feat : 계산기 과제) (0) | 2025.01.13 |
---|---|
Enum 및 예외처리를 적용한 계산기 만들기 (0) | 2025.01.10 |
Lambda & Stream을 이용하여 특정 값보다 큰 값들만 조회하기 (0) | 2025.01.09 |
제네릭을 활용한 Double타입 계산이 가능한 계산기 만들기 (0) | 2025.01.08 |
다형성을 이용한 계산기 만들기 - 예외처리 추가 (0) | 2025.01.07 |