CODING/스파르타 내일배움캠프 TIL

계산기 개인과제 관련 트러블슈팅 TIL 모음

codingTrip 2025. 1. 9. 19:49

 참고 : https://github.com/codingTrip-IT/calculator

2025.01.02(목)

Lv1 트러블 슈팅

첫 번째 오류

Process 'command '/Library/Java/JavaVirtualMachines/jdk-11.0.10.jdk/Contents/Home/bin/java'' finished with non-zero exit value 1

Intellij, Gradle 환경에서 Java 코드 작성 후 실행 시 이런 에러가 나왔다.

  1. [ Intellij IDEA> Settings] 클릭 (맥 단축키 : Command + ,)
  2. [Build, Excution, Deployment > Build Tools > Gradle] 클릭
  3. Build and run using과 Run tests using을 Gradle(Default)->Intellij IDEA로 바꿔준다.

출처 : https://velog.io/@developerjun0615/Spring-Intellij-%EC%8B%A4%ED%96%89%EC%8B%9C-finished-with-non-zero-exit-value-1-%EC%98%A4%EB%A5%98

 

두 번째 오류

Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 0

인덱스 값으로 마이너스 값을 대입하거나, 문자열 길이보다 큰 인덱스 값을 대입하면 발생한다고 한다.

출처 : https://livebyfaith117.tistory.com/120

=> 하지만, 나로서는 이 설명만 보고서는 바로 이해가 가지 않았다. 

nextLine()으로 받았을 때 문제가 발생한 것 같다고 짐작할 뿐이었다.

next()로 받았을 때는 오류가 발생하지 않았기 때문이다.

 

왜일까?

char operator = sc.next().charAt(0);
char operator = sc.nextLine().charAt(0);

* next() : 개행문자(\n)를 무시하고 입력 받음

                  즉, 숫자를 입력하고 엔터를 누를경우, 엔터 이전까지만 입력을 받음

* nextLine() : 한 줄 단위로 입력 받기 때문에, 개행문자(\n)도 한 줄로 인식함

                          char 타입의 경우 'A'과 같이 한 글자만 들어가야 하므로 오류 발생

출처 : https://limkydev.tistory.com/170

 

발제 후 점심 시간(12시) 전까지 작성한 코드

package com.example.calculator;

import java.util.Scanner;

public class Calculator {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int num1; 
        int num2;
        char calculator;
        int result = 0;
        String exit;

        while(true){
            System.out.print("첫 번째 숫자를 입력하세요: ");
            // Scanner를 사용하여 양의 정수를 입력받고 적합한 타입의 변수에 저장합니다.
            num1 = sc.nextInt();
            System.out.print("두 번째 숫자를 입력하세요: ");
            // Scanner를 사용하여 양의 정수를 입력받고 적합한 타입의 변수에 저장합니다.
            num2 = sc.nextInt();

            if(num1>=0 && num2>=0){
                System.out.print("사칙연산 기호를 입력하세요: ");
                // 사칙연산 기호를 적합한 타입으로 선언한 변수에 저장합니다.
                calculator= sc.next().charAt(0);

                switch(calculator){
                    case '+':
                        result = num1 + num2;
                        break;
                    case '-':
                        result = num1 - num2;
                        break;
                    case '*':
                        result = num1 * num2;
                        break;
                    case '/':
                        if (num2 == 0) {
                            System.out.println("나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다.");
                            break;
                        }
                        result = num1 / num2;
                        break;
                    default:
                        System.out.println("잘못된 연산자입니다. 다시 입력해주세요.");
                        break;
                }
                /* 제어문을 활용하여 위 요구사항을 만족할 수 있게 구현합니다.*/
                System.out.println("결과: " + result);

                System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
                /* exit을 입력 받으면 반복 종료 */
                exit = sc.next();
                if(exit.equals("exit")){
                    break;
                }
            }
        }


    }
}

 

레벨 1은 일단 여기서 마무리하고, 레벨 2를 3주차 강의 복습과 함께 진행하고자 한다.

 

=> 팀 내에서 회고를 진행했는데

nextInt()를 사용하면 문자 입력 시 에러가 발생한다고 말씀해주셨다.

예외처리(try catch) 4주차 강의 참고해서 작성해야겠다.

 

또한 0으로 나누기를 실행할 때, if else문보다는 예외로직을 나열하고, 정상로직을 먼저 써주면 좋을 것 같다고도 하셨다.

 

그리고 깃 사용을 연습해보라고도 하셨다.

 


2025.01.03(금)

Lv1  트러블슈팅

java.util.InputMismatchException at java.base/java.util.Scanner.throwFor(Scanner.java:939) at java.base/java.util.Scanner.next(Scanner.java:1594) at java.base/java.util.Scanner.nextInt(Scanner.java:2258) at java.base/java.util.Scanner.nextInt(Scanner.java:2212) at com.example.calculator2.App.main(App.java:19)

숫자가 아닌 다른 값을 입력했을 때 발생했다.

사실 어제 마지막으로 팀원들과 회고할 때, 다른 팀원분이 말씀해주신 사항이어서 이에 대한 예외처리를 해야 한다.

4주차 try- catch문을 사용한  예외처리를 하라고 조언을 해주셨다.

 

튜터님께서 예외처리는 4주차 강의 내용이므로 필수 과제에서는 구현 안해도 된다고 하셨다.

도전 과제까지 진행하게 될 경우, 하게 될 것으로 보인다.

Lv2  트러블슈팅

트러블슈팅 작성 방

  • 배경 : 어떤 현상을 발견해서
  • 발단 : 이런 장애가 생길 수 있다는 것을 인지했고,
  • 전개 : 장애를 대응, 해결하던 와중에
  • 위기 : 또 다른 장애 발견 또는 간단하게 해결할 수 없다는 것을 알게되어서,
  • 절정 : 근본적인 해결을 위해 이런 방법으로 접근하였다.
  • 결말 : 따라서, 이런이런 방법을 통해 근본적으로 해결 및 앞으로 유지, 보수에 용이하게 개선하게 되었다.

컬렉션 선정

  • 배경 : 사칙연산 계산의 결과값을 컬렉션에 저장해야 한다. -> 어떤 컬렉션에 저장할지 생각해야 한다. 
               Calculator 클래스에 저장된 연산 결과들 중 가장 먼저 저장된 데이터를 삭제하는 기능을 가진 메서드를 구현해야 한다.
               처음에는 FIFO가 생각나서 Queue를 선택하려고 했다.
               그러나 Queue는 인덱스를 통해 값을 불러오지 못한다.
  • 발단 : Calculator 클래스의 연산 결과를 저장하고 있는 컬렉션 필드의 값을 가져오지 못한다.
  • 전개 : 이에 다른 컬렉션을 찾아보게 되었다.
  • 위기 : 가장 먼저 저장된 데이터를 삭제해야 하기 때문에 순서가 있는 컬렉션을 선택해야 한다.
  • 절정 : 순서가 있는 컬렉션은 List가 있다.
  • 결말 : 결국 List 중 ArrayList을 선택했다.

 

가변길이의 매개변수를 어떻게 받아올 것인가?

위의 사진은 내가 고민했던 부분을 간단하게 도식화한 것이다.

Calculator 클래스의 calculte 메서드에 가변길이의 매개변수를 받고자 하니

나로서는 복잡한 생각이 들었다.

 

Q1) 매개변수 value의 타입을 String타입 배열로 받아야 하나?

Q2) App 클래스에서 num1(첫 번째 정수 입력값), num2(두 번째 정수 입력값), operator(연산자)를

       각각 int, int, char타입으로 지정했는데,

       String으로 변환해서 보내줘야 하나?

Q3) String 배열로 받아서 다시 int, int, char 타입으로 변환해야 하나?

Q4) Output은 result로? 아니면 list 형식으로 반환해야 하나?

 

비효율적으로 보이지만 나는 위의 질문사항들을 토대로 구현하기 시작했다.

 

Q1) 매개변수 value의 타입을 String타입 배열로 받기

public int calculate(String... value) {}

 

Q2) App 클래스에서 num1(첫 번째 정수 입력값), num2(두 번째 정수 입력값), operator(연산자)를

       String으로 변환해서 보내주기

String stringNum1= Integer.toString(num1);
String stringNum2= Integer.toString(num2);
String stringOperator = Character.toString(operator);

 

Q3) String 배열로 받아서 다시 int, int, char 타입으로 변환하기

int num1 = Integer.parseInt(value[0]);
int num2 = Integer.parseInt(value[1]);
char operator = value[2].charAt(0);

 

Q4) Output은 result로 반환하기, 결과값은 list 추가하기

list.add(result);
return result;

 

 

ArrayIndexOutOfBoundsException 발생

  • 배경 : ArrayIndexOutOfBoundsException이 발생했다.
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
  • 발단 : 예외의 내용을 보니, 배열의 크기와 관련된 것으로 추정했다.
  • 전개 : ArrayIndexOutOfBoundsException 관련 원인을 검색해서 찾기 시작했다.
  • 위기 : 정해진 배열의 크기보다 크거나 음수 index에 대한 요청이 있으면 ArrayIndexOutOfBoundsException이 발생한다.
  • 절정 : 배열 길이가 3이고 index가 0, 1, 2에 값이 있는데 배열의 3번 값을 달라고 했으니 오류가 났다...
  • 결말 : value[3] 항상 0부터 시작하는데 잠깐 잊고 있었나보다.
char operator = value[3].charAt(0);
-> char operator = value[2].charAt(0);

 

튜터님 피드백

가변길이 매개변수 수정하기

가변길이 매개변수는 좋은 방법이 아니므로, 다른 방법으로 구현하는 것이 좋다고 하셔서 수정했다.

그 이유는 [이펙티브 자바] 책 '가변인수는 신중히 사용하라' 부분을 참고하라고 하셨다.

 

이유 : 가변인수 메서드 호출될 때마다 배열을 새로 하나 할당하고 초기화한다.

성능에 민감한 상황이라면 가변인수가 걸림돌이 될 수도 있다고 한다.

그래서 일반적으로 많이 쓰이는 개수까지 다중정의를 하고, 그 이후는 가변인수를 사용하기도 한다고 한다.

 

+) 추가적으로 아래와 같이 두 오버로딩된 메서드가 구분되지 않아서 컴파일에러가 발생하기도 한다.

따라서 가능하면 가변인수를 사용한 메서드는 오버로딩하지 않는 것이 좋다.

static String concatenate(String delim, String... args){} //1번
static String concatenate(String... args){} //2번

출처:자바의 정석 3판 p289

// 수정 전
public int calculate(String... value) {
		// String타입의 value 배열에 값을 받아서 각각 변수에 저장
        int num1 = Integer.parseInt(value[0]);
        int num2 = Integer.parseInt(value[1]);
        char operator = value[2].charAt(0);
        }
        
// 수정 후
 public int calculate(int num1, int num2, char operator) {/*중략*/}

 

 

삭제하시겠습니까? 기능 추가

기존에는 exit 즉 종료를 하면, 가장 먼저 저장된 데이터를 바로 삭제하고 종료하게 구현했었다.

그러나 튜터님께서 삭제하시겠습니까?라고 사용자에게 물어보는 기능을 추가하라고 하셔서 아래와 같이 구현했다.

if(exits.equals("exit")){
        System.out.println("가장 먼저 저장된 데이터를 삭제하시겠습니까? y/n");
        removeOk = sc.next();
        if(removeOk.equals("y")){
           cal.removeResult();
        }else if(removeOk.equals("n")){
           System.out.println("삭제하지 않고 종료합니다.");
        }else{
           System.out.println("삭제하지 않고 종료합니다.");
        }
        break;
}

 

0으로 나누거나 사칙연산자 외 다른 값을 입력할 경우 대처방안

switch (operator) {
            case '+':
                result = num1 + num2;
                break;
            case '-':
                result = num1 - num2;
                break;
            case '*':
                result = num1 * num2;
                break;
            case '/':
                if (num2 == 0) {
                    System.out.println("나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다.");
                    break;
                }
                result = num1 / num2;
                break;
            default:
                System.out.println("잘못된 연산자입니다. 다시 입력해주세요.");
                break;
        }

위와 같이 작성했을 때, App 단독 클래스에서 작동할 때는 잘 되었지만

이 계산 기능을 Calculator 클래스의 계산 메소드에서 처리하게 될 경우에는

println은 잘 나오지만 리스트에 해당 값을 0으로 저장하게 되는 문제가 발생했다.

 

switch (operator) {
    case '+':
        result = num1 + num2;
        System.out.println("결과: "+num1+operator+num2+"=" + result);
        resultList.add(result);
        break;
    case '-':
        result = num1 - num2;
        System.out.println("결과: "+num1+operator+num2+"=" + result);
        resultList.add(result);
        break;
    case '*':
        result = num1 * num2;
        System.out.println("결과: "+num1+operator+num2+"=" + result);
        resultList.add(result);
        break;
    case '/':
        if (num2 == 0) {
            System.out.println("나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다.");
            break;
        }
        result = num1 / num2;
        System.out.println("결과: "+num1+operator+num2+"=" + result);
        resultList.add(result);
        break;
    default:
        System.out.println("사칙연산자가 아닙니다. 다시 입력해주세요.");
        break;
}

 

따라서 위와 같이 작성을 했다.

문제 상황이 아닌 부분에는 일일이 list에 결과값을 추가했다.

코드의 가독성이 좀 안 좋아보이지만

일단 문제는 해결했으니 리팩토링에 관한 부분은 다음의 과제로 만들겠다.

 

레벨 2는 다음주까지 시간이 소요될 것이라고 생각했었다.

그런데 생각보다 구현을 하고, 어느 정도 기능의 문제는 없어서

왜 이러지? 뭔가 빠뜨린 것 이 있나?하고 의아해하는 날이었다.

그래서 계속 튜터님께 확인받기도 했다.

생각보다 일이 잘 풀려도 불안하다. :)


2025.01.06(월)

Lv3  트러블슈팅

Enum 타입

강의에서는 해당 타입에 대해 제대로 배우지 못한 것 같다.

그래서 이에 대해 공부하는 것부터 시작해야 해야 한다.

public enum OperatorType {+,-,*,/} //error
public enum OperatorType {PLUS,MINUS,MULTIPLY,DIVIDE}

 

위와 같이 상수의 형태로 지정해주는 것을 알 수 있다.

출처 : https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%97%B4%EA%B1%B0%ED%98%95Enum-%ED%83%80%EC%9E%85-%EB%AC%B8%EB%B2%95-%ED%99%9C%EC%9A%A9-%EC%A0%95%EB%A6%AC

 

ArithmeticCalculator 클래스에서 어떻게 불러올지도 고민이었다.

그래서 튜터님께 문의를 드렸다.

튜터님께서는 calculate 메서드 안에서 enum으로 변환하는 코드를 쓰면 좋겠다고 하셨다.

그래서 연산자(operator)를 String 타입으로 변환하고

valueOf() : 매개변수로 주어진 String(operator)과 열거형에서 일치하는 이름을 갖는 원소를 반환했다.

    public int calculate(int num1, int num2, char operator) {

        OperatorType op = OperatorType.valueOf(Character.toString(operator));

        switch (op) {
            case PLUS:
                result = num1+num2;
                break;
            case MINUS:
                result = num1 - num2;
                break;
            case MULTIPLY:
                result = num1 * num2;
                break;
            case DIVIDE:
                if (num2 == 0) {
                    System.out.println("나눗셈 연산에서 분모(두번째 정수)에 0이 입력될 수 없습니다.");
                    break;
                }
                result = num1 / num2;
                break;
            default:
                System.out.println("잘못된 연산자입니다. 다시 입력해주세요.");
                break;
        }

 

그랬더니 아래와 같은 예외사항이 발생했다.

Exception in thread "main" java.lang.IllegalArgumentException: No enum constant com.example.calculator3.OperatorType.- at java.base/java.lang.Enum.valueOf(Enum.java:273) at com.example.calculator3.OperatorType.valueOf(OperatorType.java:3) at com.example.calculator3.ArithmeticCalculator.calculate(ArithmeticCalculator.java:74) at com.example.calculator3.App.main(App.java:26)

주어진 String과 일치하는 원소가 없는 경우 IllegalArgumentException 예외 발생한다고 한다.

예를 들어 operator는 +이고, enum은 PLUS 등이 있다.

+와 일치하는 이름을 갖는 원소는 열거형에 없으므로 문제가 발생한 것으로 보인다.

 

그래서 아래와 같이 수정했다.

// 매개변수가 null이 아니고 enum의 값이 매개변수와 같으면 enum의 값을 리턴하라
    public static OperatorType findByVal(String val) {
        for (OperatorType v : values()) {
            if (val != null && val.equals(v.value)) {
                    return v;
            }
        }
        return null;
    }

enum의 값을 각각 가져오고

그 값이 매개변수와 같을 경우에 반환해주는 것으로 바꾸었다.

출처:https://carpet-part1.tistory.com/786

 

따라서 오늘까지 구현한 OperatorType Enum의 최종 코드는 아래와 같다.

package com.example.calculator3;

public enum OperatorType {
    PLUS("+"), MINUS("-"), MULTIPLY("*"),DIVIDE("/");

    //값을 저장할 필드(인스턴스 변수)를 추가
    private final String value;
    // 생성자를 추가
    OperatorType(String value){this.value = value; }

    public String getValue(){return value;}

    public static OperatorType findByVal(String val) {
        for (OperatorType op : values()) {
            if (val != null) {
                if (val.equals(op.value)) {
                    return op;
                }
            }
        }
        return null;
    }
}

 

참고

Git 커밋 메시지 컨벤션 - https://velog.io/@shin6403/Git-git-커밋-컨벤션-설정하기

  • feat: 새로운 기능 추가
  • fix: 버그 수정
  • docs: 문서 관련 변경 (주석 포함)
  • style: 코드 포맷팅, 세미콜론 추가 등 기능에 영향을 미치지 않는 변경
  • refactor: 코드 리팩토링 (기능 변경 없음)
  • test: 테스트 추가 또는 수정
  • chore: 빌드 과정 또는 도구 관련 설정 변경

git commit -m Feat: "Lv3 과제 Enum 타입을 활용하여 연산자 타입에 대한 정보를 관리📰”

처음 써보는 것이라 위처럼 쓰게 되어 오류가 발생했다.

 

git commit -m “Feat: Enum 타입을 활용하여 연산자 타입에 대한 정보를 관리 기능 구현📝"

이렇게 최대한 이모지와 함께 쓰는 습관을 들여야겠다.

 


2025.01.07(화)

Lv3  트러블슈팅

제네릭

지름길로 가려고 하다보니 엉망진창이다.

그래서 다시 해당 내용을 찬찬히 복습하기로 했다.

 

제네릭의 장점

1. 타입 안정성을 제공한다.

2. 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해 진다.

실행시 발생 에러가 아닌 컴파일 에러로 단계를 낮출 수 있다.

 

제네릭 클래스를 작성할 때, Object타입(일반클래스) 대신 타입변수(제네릭 클래스)를 선언해서 사용한다.

 

제네릭의 용어

class Box<T>

Box<T> : 제네릭 클래스

T : 타입 변수 또는 타입 매개변수

Box : 원시 타입(일반 클래스)

 


2025.01.08(수)

팀원분의 특강 :)

팀원분께서 우리 팀에서 레벨 3 과제에 대해 매우 어려워하는 것을 보시고

특강이라는 큰 도움을 주셨습니다. :)

김영한님 자바 강의를 참고하셨다고 합니다.

저의 경우에는 구글링을 통해 일부 과제를 수행했지만

정확한 개념 정리가 잘 되지 않던 터라 정말 반가웠습니다.

 

제네릭

먼저 왜 써야 하는지? 생각해봐야 합니다.

아래의 예시를 통해 살펴보겠습니다.

public class IntegerBox {
    private Integer value;
    
    public Integer get() {
   		return value;
    }
    
    public void set(Integer value) {
    	this.value = value;
    }
}

Tip) cmd+shift+Enter : 자동완성

public class StringBox {
    private String value;
    
    public String get() {
    	return value;
    }
    
    public void set(String object) {
    	this.value = object;
    }
}

이렇게 각각 정수와 문자열을 보관하고 가져올 수 있는 클래스를 만들었습니다.

 

public class Main {
    public static void main(String[] args) {
        IntegerBox integerBox = new IntegerBox();
        integerBox.set(10); //오토 박싱
        Integer integer = integerBox.get();
        System.out.println("integer = " + integer);
        
        StringBox stringBox = new StringBox();
        stringBox.set("hello");
        String str = stringBox.get();
        System.out.println("str = " + str);
    }
}

정수 타입의 값을 IntegerBox에

문자열 타입의 값을 StringBox에 각각 넣어서 출력해보았습니다.

 

문제점

`Double` , `Boolean`등 타입만 바뀌었을 뿐 메서드 등이 동일한 이 클래스를 재사용하는 방안은 없을까요?

`DoubleBox`BooleanBox` 와 같이 클래스를 계속 새로 만들어야 할까요?

 

1차 해결

상속, 다형성 개념 이해가 필요합니다.

모든 타입을 다 받을 수 있는 Object 타입으로 Box를 만들어 줍니다.

public class ObjectBox {
    private Object value;
    
    public Object get() {
    return value;
    }
    
    public void set(Object object) {
    this.value = object;
    }
}

 

public class Main {
    public static void main(String[] args) {
        ObjectBox integerBox = new ObjectBox();
        integerBox.set(10);
        Object obj = integerBox.get(); // 에러
        Integer integer = (Integer) integerBox.get(); //Object -> Integer 캐스팅
        System.out.println("integer = " + integer);
        
        ObjectBox stringBox = new ObjectBox();
        stringBox.set("hello");
        String str = (String) stringBox.get(); //Object -> String 캐스팅
        System.out.println("str = " + str);
        
        //잘못된 타입의 인수 전달시
        integerBox.set("문자100");
        Integer result = (Integer) integerBox.get(); // String -> Integer 캐스팅 예외
        System.out.println("result = " + result);
    }
}

이렇게 Object타입을 활용할 경우, 자식은 부모를 담을 수 없기 때문에

어떤 타입으로 사용할지 강제형변환을 꼭 해야 합니다.

 

잘못된 타입의 인수를 전달할 시, 컴파일상에서는 문제를 잡아내지 못합니다.

따라서 컴파일 에러가 아닌 실행  시 에러가 발생합니다.

컴파일상에서 에러를 잡을 수 있는 것이 더 좋기 때문에

Object타입을 사용하는 것은 바람직하지 않습니다.

=> 재사용성 good, 타입 안정성 bad

 

2차 해결

따라서 컴파일상에서 타입 불일치 오류를 잡아낼 수 있는 제네릭을 사용합니다.

보통 T를 많이 사용하는데, 여기에서는 이해하기 쉽게 Type이라고 임의로 사용하겠습니다.

public class GenericBox<Type> {
    private Type value;
    
    public Type get() {
    return value;
    }
    
    public void set(Type value) {
    this.value = value;
    }
}
public class Main {
    public static void main(String[] args) {
        GenericBox<Integer> integerBox = new GenericBox<Integer>(); //생성 시점에 T의 타입 결정
        integerBox.set(10);
        //integerBox.set("문자100"); // Integer 타입만 허용, 컴파일 오류
        Integer integer = integerBox.get(); // Integer 타입 반환 (캐스팅 X)
        System.out.println("integer = " + integer);
        
        GenericBox<String> stringBox = new GenericBox<String>();
        stringBox.set("hello"); // String 타입만 허용
        String str = stringBox.get(); // String 타입만 반환
        System.out.println("str = " + str);
    }
}

위와 같이 제네릭 클래스를 활용하면, <> 꺽쇠 안에 다양한 타입을 넣어서 다양하게 확장 가능합니다.

 

(이에 대한 예시를 우리 팀은 대체로 게임을 좋아해서 게임을 예시로 들어주셨다.)

 

먼저 아무 팀이나 나타낼 수 있는 Team 클래스를 만들었습니다.

public class Team {
    private Team teamName;
    
    public String getTeamName(){
    	return "익명팀"
    }
}

 

public class T1 extends Team {
    private String teamName;
    
    @Override
    public String getTeamName(){
    	return "T1"
    }
}
public class Hwe extends Team {
    private String teamName;
    
    @Override
    public String getTeamName(){
    	return "한화생명"
    }
}

T1과 Hwe는 각각 Team 클래스를 상속받아서 오버라이딩 합니다.

 

public class Fan{
    private Team team;
    
    public Fan(Team team){
    	this.team = team;
    }
    
    public void cheer(){
    	System.out.println("화이팅" + team.getTeamName();)
    }
}

팬 클래스도 만듭니다.

팀 객체를 받아서 각 팀을 응원할 수 있는 메서드도 만듭니다.

 

public class Main {
    public static void main(String[] args) {
        Team t1 = new T1(); //부모는 자식을 품을 수 있다. 다형성 Integer->Obeject같이 상위
      //T1 team = new Team(); // 에러 자식은 부모를 품을 수 없다.
        Fan t1Fan = new Fan(t1);
        t1Fan.cheer();
        // 설정한 메서드 말고도 다른 메서드 사용가능한 이유
        // 내부적으로 Object를 상속받기 때문
        
    }
}

 

만약 제네릭을 사용한다면?

public class Fan<Type>{
    private Type team;
    
    public Fan(Type team){
    	this.team = team;
    }
    
    public void cheer(){
    	System.out.println("화이팅" + team.getTeamName();)
    }
}

위와 같은 경우 모든 타입이 가능하니까 내부적으로는 Object로 만들어집니다.

Object타입에서는 team.getTeamName()를 사용할 수 없습니다.

따라서 Team과 그 자손만 가능하도록 범위를 제한해야 합니다.

그래야 공용 메서드를 사용 가능합니다.

 

<Type> -> Object, Integer, Double... 다 가능

<Type extends Team> -> 적어도 Team 밑에만 넘어올 수 있습니다.

public class Fan<Type extends Team>{
    private Type team;
    
    public Fan(Type team){
    	this.team = team;
    }
    
    public void cheer(){
    	System.out.println("화이팅" + team.getTeamName();)
    }
}
public class Main {
    public static void main(String[] args) {
        T1 t1 = new T1();
        Fan<T1> t1Fan = new Fan<>();
        t1Fan.cheer();
    }
}

이로써 제네릭을 사용하면 재사용성도 good, 타입 안정성도 good이 됩니다.

 

추가> 다형성 개념 정리

Team t1 = new T1();

t1은 T1, Team 객체도 생성됩니다.

따라서 무조건 선언한 부모인 Team 타입을 따라 갑니다.

그런데 T1에서 오버라이드한 것이 있다면 T1의 것을 사용합니다.

 

Enum

왜 사용해야 할까요?

아래의 예시를 통해 살펴보겠습니다.

public Calss AuthService{
    public String authCheck(String authGrade) {
        if (authGrade.equals("ADMIN")) {
        	return "관리자 화면";
        } else if (authGrade.equals("LOGIN")) {
            return "메인 화면";
        } else if (authGrade.equals("GUEST")){
            return "로그인 화면";
        } else {
     	   return null;
        }
    }
}

관리자면 관리자 화면을, 회원이면 메인화면을, 일반 방문자면 로그인 화면을 보여주도록 처리하는 클래스를 만듭니다.

public class Main {
    public static void main(String[] args) {
    	AuthService as = new AuthService();
        
        String s = as.authCheck("VIP");
        String s1 = as.authCheck("ADMIN");
        String s2 = as.authCheck("GUEST");
        String login = as.authCheck("login");
    }
}

이럴 경우에 일반 신입 개발자는 내부정보(ADMIN, GUEST, LOGIN 사용한다는 사실)를 모르므로

그저 String 타입 형식만 지키면 되는 것으로 생각하고 VIP, long을 설정합니다.

 

1차해결 - 상수

public Calss AuthGrade{
	public static final String ADMIN = "ADMIN";
	public static final String LOGIN = "LOGIN";
    public static final String GUEST = "GUEST";
}

위와 같은 문제를 해결하기 위해 상수라는 개념을 생각해보았습니다.

그러나 결국 상수로 지정해도, 신입 개발자는 내부정보(ADMIN, GUEST, LOGIN 사용한다는 사실)를 모르므로

그저 String 타입으로 입력하는 실수가 발생합니다.

 

public Calss AuthGrade{
	private String grade;
	
    public static final AuthGrade ADMIN = new AuthGrade("ADMIN");
	public static final AuthGrade LOGIN = new AuthGrade("LOGIN");
    public static final AuthGrade GUEST = new AuthGrade("GUEST");
    
    public AuthGrade(String grade){
    	this.grade = grade;
    }
}

String타입을 AuthGrade로 지정해주었습니다.

 


위와 같이 사용하면 미리 정한 3개의 상수가 아닌 다른 값을 입력했을 때, 컴파일 단계에서 에러가 발생합니다.(빨간줄)

이를 통해 신입 개발자는 문제점을 확인할 수 있습니다.

public enum AuthGrade {
	private final String grade;
	
    ADMIN("ADMIN"),LOGIN("LOGIN"),GUEST("GUEST");
    
    public AuthGrade(String grade){
    	this.grade = grade;
    }
}

 

마지막으로 아래와 같이 말씀해주셨습니다.

1. 왜 써야 하는지?

2. 이런 상황 발생 시 무엇을 쓸지 판단하기


과제 이해하기

Generic

public class ArithmeticCalculator<T extends Number>{}

int와 같은 정수나 double과 같은 실수 값을 받아오기 위해서는

String과 같은 숫자가 아닌 타입은 받아오지 못하도록 제한한다.

 

Enum

public enum OperatorType {
    PLUS('+'), MINUS('-'), MULTIPLY('*'),DIVIDE('/');

    //값을 저장할 필드(인스턴스 변수)를 추가
    private final char value;
    // 생성자를 추가
    private OperatorType(char value){this.value = value; }

    public char getValue(){return value;}

    // 매개변수가 null이 아니고 enum의 값이 매개변수와 같으면 enum의 값을 리턴하라
    public static OperatorType findByVal(char val) {
        OperatorType[] values = OperatorType.values();
        for (OperatorType v : values) {
            if (val==v.value) {
                    return v;
            }
        }
        return null;
    }
}

 

OperatorType.values()

 : 모든 enum 값을 반환

배열로 만들어서 확장된 for문을 사용하여 각각의 요소를 출력할 수 있음

매개변수 val의 값과 enum의 value의 값이 같으면 enum 상수를 반환한다.

 

추가로 알아두면 좋을 enum 메서드

OperatorType.valueOf()

String input = "+";
OperatorType plus = OperatorType.valueOf(input)

String -> ENUM 변환,

잘못된 문자인 경우, IllegalArgumentException 발생한다.

그래서 내가 과거에 해당 메서드를 사용했을 때 위와 같은 오류가 발생했다.

참고:https://codingtrip.tistory.com/83


2025.01.09(목)

오늘은 리팩토링, 주석 및 README 작성 위주로 진행했기 때문에

큰 문제는 발생하지 않아서 한계점에 대해 말씀드리겠습니다.

 

1) 0으로 나누면 Infinity가 되는 원인 파악하기

과제 실행 결과



if문을 사용해서 0으로 나누지 못하도록 처리했으나,
사실은 try-catch문 ArithmeticException을 사용하고 싶었습니다.
그러나 0으로 나눌 경우, Exception이 아닌 Infinity로 받아져서 예외처리가 안되므로 if문으로 처리했습니다.

 

2) 메서드 반환타입 제네릭을 사용해보기

calculator 메서드에서 반환타입을 제네릭으로 받고 싶었으나,
return result 부분에서 형변환을 하는 방법을 찾지 못해서
double타입으로 두었습니다.

 

3) 람다&스트림 더 활용하기

Scanner로 입력받은 값보다 결과값 리스트 값이 더 큰 경우 그 값을 출력하도록 구현했습니다.
추후 입력값이 결과값보다 작은 경우 작다는 메시지 출력을 스트림을 사용해서 구현하고 싶습니다.

 

튜터님 특강

List 타입으로 받아오기

private ArrayList<Double> resultList = new ArrayList<>();
private List<Double> resultList = new ArrayList<>();

기존에는 위처럼 ArrayList 타입으로 받았었는데,

이보다는 List타입으로 받는 것이 더 좋다고 말씀해주셔서 수정했다.

추후에 LinkedList로 바꿀 수도 있으므로 확장성을 좋게하기 위해서로 하셨다.