peaches-book-study / effective-java

이펙티브 자바 3/E
0 stars 2 forks source link

Item 35. ordinal 메서드 대신 인스턴스 필드를 사용하라 #35

Open jseok0917 opened 3 months ago

jseok0917 commented 3 months ago

🍑 서론

정수, 문자열 상수가 아닌 열거 타입(enum type)을 사용하라

Enum

  1. 상수를 표현하기 위해 사용되는 클래스의 특별한 형태(멤버변수O, 생성자O, 메서드O)
    • 모든 열거형의 부모클래스
    • 타입안정성과 유지보수성, 가독성 제공
      • 코드가 단순해지며 가독성이 좋다.
      • 인스턴스 생성, 상속을 방지하며 상수값의 타입 안전성이 보장된다.
      • enum이라는 키워드로 열거형의 의도를 명확히 드러낸다.
    • 다른 클래스와 마찬가지로 java파일, 클래스 안, 클래스 밖 선언 가능
  2. 서로 연관된 상수들의 집합
  3. Enum 클래스 형을 기반으로 한 클래스형 선언(모든 상수클래스의 부모클래스 = enum)

enum vs static final

  1. 코드에 주석이 없을 경우 코드를 이해하기 어려움

    int type = 1;
    if(type == 1) {
    System.out.println("boy");
    } else {
    System.out.println("girl");
    }
    //type에 대한 주석이나 if문 안에서 처리하는 내용에 대한 주석이 없으면 코드를 이해하기 어렵다.

     

  2. 해결방안 >  final static을 설정한다.

private final static int BOY = 1;
private final static int GIRL = 2;

public static void main(String[] args) {

    int type = 1;
    if(type == BOY) {
        System.out.println("boy");
    } else if(type == GIRL){
        System.out.println("girl");
    }
}
//final static으로 설정하면 주석없이도 의미를 파악할 수 있다.


private final static int BOY = 1;
private final static int GIRL = 2;

private final static int MONDAY = 1;
private final static int TUESDAY = 2;
// 그러나 같은 상수명을 갖는 다른의미의 값이 존재하거나 다른 상수명이지만 같은 값을 가지는 경우가 있을 수 있고 에러가 발생할 수 있다.
// 각각의 상수들은 자신을 인스턴스화 한 값을 할당한다.
// BOY와 MONDAY는 서로 다른 데이터를 의미하는데 비교할 경우
if(BOY == MONDAY) {
...
}
//컴파일 에러가 발생하지 않고 런타임 단계에서 생각지 못한 문제를 발생시킬 수 있다.


  1. 해결방안 > class로 작성된 상수를 인스턴스화하여 구체화 한다.
class SEX {
    public final static SEX BOY = new SEX(1);
    public final static SEX GIRL = new SEX(2);

    int num;

    public SEX(int num) {
        this.num = num;
    }

}

class DAY {
    public final static DAY MONDAY = new DAY();
    public final static DAY TUESDAY = new DAY();
}
// 인스턴스화하면 자신의 타입으로 비교하기 때문에 컴파일시 에러를 확인할 수 있다.
int type = 1;
switch (type) {
    case DAY.MONDAY:
        System.out.println("월요일");
        break;
    case DAY.TUESDAY:
        System.out.println("화요일");
        break;
}
//그런데 switch문에 사용할 수 없다.


enum SEX {
    BOY(1), GIRL(2);

    private int sex;

    SEX(int type) {
        sex = type;
    }

    public int getSex() {
        return sex;
    }
}

enum DAY {
    MONDAY, TUESDAY
}

SEX type = SEX.BOY;
switch (type) {
    case BOY:
        System.out.println("boy");
        break;
    case GIRL:
        System.out.println("girl");
        break;
}

🍑 본론

ordinal을 잘못 사용한 예

public enum Esenmble {

    //SOLO는 0, DUET은 1, ...
    SOLO, DUET, TRIO, QUARTET, QUINTET,
    SEXTET, SETPTET, OCTET, NONET, DECTET;

    public int numberOfMusicians() {
        return ordinal();
        //return orinal() + 1;
    }

    public static void main(String[] args) {
        Ensemble solo = Ensemble.SOLO;
        System.out.println(solo.numberOfMusicians()); // 0 출력
    }

}
//동작은 하지만 유지보수하기가 끔찍하다.

문제점이 무엇이냐?

//예시1
public enum Esenmble {
    //SOLO(=1명)와 DUET(=2명)의 위치를 바꾼다면?
    DUET, SOLO, TRIO, QUARTET, QUINTET,
    SEXTET, SETPTET, OCTET, NONET, DECTET;
    //DUET이 1로 출력돼버리는 상황 발생
    //순서를 바꿔서는 안된다!
}

//예시2
public enum Esenmble {
    //3중 4중주(=12명으로 구성)를 추가한다면?
    DUET, SOLO, TRIO, QUARTET, QUINTET,
    SEXTET, SETPTET, OCTET, NONET, DECTET, TRIPLE_QUARTET;

    public int numberOfMusicians() {
        return orinal() + 1;
    }

    public static void main(String[] args) {
        Ensemble triple_quartet = Ensemble.TRIPLE_QUARTET;
        System.out.println(triple_quartet.numberOfMusicians()); // 11 출력
        //값을 중간에 비워둬서도 안된다.
    }

}

//예시3
public enum Esenmble {
    //복사중주(2*4 = 8명)를 추가한다면?
    DUET, SOLO, TRIO, QUARTET, QUINTET,
    SEXTET, SETPTET, DOUBLE_QUARTET,
    OCTET, NONET, DECTET;
    //복사중주는 8로 출력되지만 그 뒤에것들은 1씩 값이 밀려나서
    //의도된 것과 다르게 numberOfMusicians 메서드가 작동
}


해결책은?

public enum Ensemble {
    //위의 문제점(순서 변경, 중간값 비워짐, 값 중복)이 모두 해결
    DUET(2), SOLO(1), TRIO(3), QUARTET(4), QUINTET(5),
    SEXTET(6), SETPTET(7), DOUBLE_QUARTET(8),OCTET(8), NONET(9), DECTET(10), TRIPLE_QUARTET(12);

    private final int numberOfMusicians; //각 앙상블 타입을 구성하는 뮤지션들의 숫자를 필드변수로 추가
    Ensemble(int size) { this.numberOfMusicians = size } // 각 타입에 뮤지션들의 숫자를 넣도록 강제

    //ordinal() 메서드를 사용하지 않고, 인스턴스 필드를 이용
    public int numberOfMusicians() { 
        return numberOfMusicians 
    }
}


그럼 ordinal() 메서드는 왜 만들어놓은걸까?

import java.util.EnumMap;

public class test03 {
    public enum Day {
        SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY;
    }

    public static void main(String[] args) {
        //EnumMap : Enum을 키로 갖는 Map의 일종
        //Enum의 각 상수들은 고유한 순서를 가지고,
        //그 순서와 개수가 불변이므로
        //이를 바탕으로 EnumMap을 내부적으로 배열을 이용하여 구현
        EnumMap<Day, String> schedule = new EnumMap<>(Day.class);

        // 일정을 추가할 때 ordinal() 메서드를 사용하여 순서를 기반으로 일정을 추가
        schedule.put(Day.MONDAY, "Network Study");
        schedule.put(Day.TUESDAY, "LOSTARK");
        schedule.put(Day.WEDNESDAY, "SSAFY PROJECT");

        // Enum클래스의 values 메서드는 정의된 모든 상수를 배열로 반환(이때 ordinal메서드 이용)
        // 요일을 출력할 때 ordinal() 메서드를 사용하여 순서를 표현
        for (Day day : Day.values()) {
            String event = schedule.get(day);
            if (event != null) {
                System.out.println(day + ": " + event);
            }
        }
    }
}



🍑 결론