자바 개인 과제(계산기 Lv.3)를 진행하면서 겪었던 어려움에 대해 적어보려고 한다.
💥문제점
기능 가이드에 제네릭을 사용하여 실수도 연산이 가능하도록 구현하는 부분이 있었다. enum으로 연산자를 관리하기 때문에 연산도 해주면 좋을 것 같다는 생각이 들었다. 실수도 연산이 가능하도록이란 내용을 보고 인터페이스에 제네릭을 사용해서 오버라이드를 하니 매개변수 타입이Object
로 나왔다.public interface InterfaceOperation<T> { double operate(T firstNumber, T secondNumber); } public enum OperatorType implements InterfaceOperation { ADD('+') { @Override public double operate(Object firstNumber, Object secondNumber) { return 0; } }
이렇게 되면 연산이 불가능하기 때문에 인터페이스에서 제네릭을 사용하는 것은 어렵다고 생각하고 다른 방법을 찾았다. 그렇게 찾은 방법은 제네릭 클래스를 만드는 것이었다.
하지만...
✅ 해결책
구현체에서 사용을 잘못하고 있었다. 다 완성하고 난 뒤 추가적인 구글링과 GPT에 물어봐서 알게 됐다.public interface InterfaceOperation<T> { double operate(T firstNumber, T secondNumber); } public enum OperatorType implements InterfaceOperation<Long> { ADD('+') { @Override public double operate(Long firstNumber, Long secondNumber) { return 0; } }
위의 예시처럼 구현체에서 인터페이스의 제네릭 타입을 정해주면 오버라이드를 했을 때
Object
타입이 나오지 않는다. 물론 이번 경우엔 구현체가 enum이었기 때문에 사용을 제대로 했어도 기능 가이드대로 구현을 되지 않았을 것 같다.
💥 문제점
구글링과 GPT의 도움으로 제네릭 클래스를 기반으로 과제를 완성 후 테스트를 진행하는데 갑자기 에러가 났다. 기능 가이드에 연산 결과를 저장한 컬렉션을 수정하는 내용이 있는데 거기서IndexOutOfBoundsException
에러가 나는 것이다.public static void main(String[] args) { Scanner scanner = new Scanner(System.in); // 정수 연산 처리 ArithmeticCalculator<Integer> intCalculator = new ArithmeticCalculator<>(); // 실수 연산 처리 ArithmeticCalculator<Double> doubleCalculator = new ArithmeticCalculator<>(); ... Random random = new Random(); // 에러 발생 부분 int ranNum = random.nextInt(resultList.size()); double ranVal = random.nextDouble(); System.out.println("========== 저장된 연산 랜덤 변경 =========="); calculator.setResultList(ranNum, ranVal); }
✅ 해결책
해결방법이라고 말하기도 민망할 정도지만 정수와 실수의 연산을 나눈다고 객체도 따로 만들어서 연산 결과가 따로 저장되었기 때문이다. 나눠서 할거면 연산 결과 얻어오는 것도 나눴어야 하는데 연산 결과를 얻어와서 수행하는 기능들은 실수 연산을 담당하는 객체에서만 얻어왔다... 원인을 인지하고 난 후에는Number
타입의 객체 하나만 생성하는 것으로 수정했다.
입력 받을 때 Number로 받아서 parser를 통해 넘겨주는데
계산은 다 doublevalue로 해버림
의미가 없지 않나
정수 / 실수를 구분하는 방법이 뭐가 있나
💥 문제점
구현을 완료하고 난 뒤 테스트를 해보는데2 + 8 = 10.0
으로 나오는 것이 신경 쓰였다. 그래서 결과값이 정수 연산은 정수로, 실수 연산은 실수로 나오도록 연산 부분을 수정했다. 원하는대로 결과는 나왔는데 사실 결과값 깔끔하게 만든다고 안 해도 되는 형변환과 제네릭을 남발한 모양새가 되었다. 제네릭을 잘 모르는 상태여서이건 좀 별로인거 같은데 vs 구분하는게 기능 가이드 내용에 더 가까운 것 같은데
로 고민을 하다가 기용 튜터님께 질문을 하게 되었다.public T calculate(T firstNumber, T secondNumber, OperatorType operator) { // 두 개의 입력값이 모두 정수인 경우, 결과값을 정수로 처리 if (firstNumber instanceof Long && secondNumber instanceof Long) { long result = (long) operator.operate(firstNumber.longValue(), secondNumber.longValue()); this.resultList.add(result); return (T) Long.valueOf(result); } else { // 입력값 중 하나라도 실수가 있는 경우, 결과값을 실수로 처리 double result = operator.operate(firstNumber.doubleValue(), secondNumber.doubleValue()); this.resultList.add(result); return (T) Double.valueOf(result); } }
✅ 해결책
정적 언어 타입인 자바에 제네릭을 남용하는 것은 좋지 않을 수 있다는 피드백을 주셨고 나도 동의했다. 원래 구현했던 방식 (숫자 중 가장 큰 double로 다 처리하는 것)으로 되돌리고, 제네릭을 사용하지 않은 이유를 주석으로 추가했다. 과제 제출 후에 다른 튜터님의 피드백을 받으면 조금 더 나은 방향을 찾을 수 있지 않을까 생각한다.public double calculate(T firstNumber, T secondNumber, OperatorType operator) { // 입력값이 정수/실수인 경우에 따라 연산 결과도 정수/실수로 리턴할 수 있지만, // 정적 언어 타입인 자바에 무분별한 제네릭 사용은 좋지 않을 수 있다고 생각하여 double로만 연산 실행 double result = operator.operate(firstNumber.doubleValue(), secondNumber.doubleValue()); this.resultList.add(result); return result; }