JAVA/Effective Java

Enum - 규칙 30 int 상수 대신 enum을 사용하라.

반응형

기존의 프로젝트에서 
자주 사용 사용하는 설정 값이나 이름들을 열거 자료형으로서 다음같이 사용했다.



1
2
3
4
5
6
7
// int를 사용한 enum 패턴
public static final int FAIL = 1;
public static final int SUCCESS = 2;
 
// string을 사용한 enum 패턴
public static final String FEMAIL = "3";
public static final String MAIL = "4";
cs



int를 사용한 설정 값은 int enum 패턴, 문자열로 되어 있는 경우에는 string enum 패턴 이라고 한다.

하지만 이런 설정은 다음과 같은 단점이 있다.

1. 편의성이 떨어진다.
2. 이는 컴파일 시점 상수(compile-time constant)이기 때문에 상수를 사용하는 클라이언트와 함게 컴파일 되어야 하기 때문에 상수의 값이 변경되면 다시 컴파일 해야한다.
※ final이 붙은 변수는 컴파일 시점 상수라고 한다.

하지만 java 1.5버전에 enum 타입이 생기고 나서는 더 이상 이렇게 사용할 필요가 없다.


Enum type
Enum type의 열거 상수별로 하나의 객체를 public static final 필드 형태로 제공한다.

또한
Enum은 원래 변경 불가능하므로 모든 필드는 final로 선언되어야 한다. 

Enum 상수에 데이터를 넣으려면 객체 필드를 선언하고 생성자를 통해 받은 데이터를 그 필드에 저장하면 된다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
public enum SEX {
    FEMAIL("NO-ARMY"),
    MAIL("ARMY");
    
    private String variable;
    
    private SEX(String variable) {
        this.variable = variable;
    }
    
    public String getVariables() {
        return this.variable;
    }
}
cs




만약 Enum 자체가 특정 클래스안에서만 필요하다면 별도로 생성할 필요 없이 
해당 클래스 내부에 생성하여 사용하면 된다.



1
2
3
4
5
6
7
8
9
10
11
public class PNP {
    
    private enum team {
        WEB,
        RCP;
    }
    
    public static void main(String args[]) {
        System.out.println(team.WEB.toString());
    }
}
cs



만약 Enum 상수 각각에 사용되는 메소드를 다음과 같이 선언 할 수 있다.

하지만 이런 방식은 좋은 방식 아니다.

왜냐하면 
만약 현재 Enum상수가 다음 apply 메서드 switch 안에 정의가 안되있으면 throw가 발생해야 하고, 만약 Enum상수가 추가되면 switch도 추가해야하는 문제가 발생할 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public enum Operation {
    PLUS,
    MINUS,
    TIMES,
    DIVIDE;
 
    double apply(double x, double y) {
        switch(this) {
            case PLUS:
                return x + y;
            case MINUS:
                return x - y;
            case TIMES:
                return x * y;
            case DIVIDE:
                return x / y;
        }
        throw new AssertionError("Unknown op: " + this);
    }
}
 
 
public class Main {
    public static void main(String args[]) {
        System.out.println(Operation.DIVIDE.apply(1030));
    }
}
cs




이런 문제를 각 Enum 상수 안에 
abstract 메소드를 구현하게 하는 각 상수별 클래스몸체(constant-specific class body)안에서 실제 메서드로 재정의할 수 있다.

이런 메서드는 상수별 메서드 구현이라고 부른다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public enum Operation {
    PLUS("+") {
        @Override
        public double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS("-") {
        @Override
        public double apply(double x, double y) {
            return x - y;
        }
    },
    TIMES("*") {
        @Override
        public double apply(double x, double y) {
            return x * y;
        }
    },
    DIVIDE("/") {
        @Override
        public double apply(double x, double y) {
            return x / y;
        }
    };
    
    private String variable;
    
    private Operation(String variable) {
        this.variable = variable;
    }
 
    public String getVariable() {
        return variable;
    }
 
    public abstract double apply(double x, double y);
}
cs




하지만 이런 상수별 메서드 구현의 단점은 enum 상수끼리 공유하는 코드를 만들기가 어렵다.

그래서 만약 여러 상황에 의해서 동작하는 enum타입 자체의 메서드를 만들고 싶은 경우에는 중첩 Enum을 만들어서 사용하는 것이 좋다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public enum Pay {
    SAWON(WorkTime.WEEKDAYS, 10),
    DARI(WorkTime.HOLIDAY, 20);
    
    private WorkTime workDay;
    private int workTime;
    
    private Pay(WorkTime workDay, int workTime) {
        this.workDay = workDay;
        this.workTime = workTime;
    }
    
    double getPay() {
        return this.workDay.getPay(workTime);
    }
    
    private enum WorkTime {
        WEEKDAYS {
            @Override
            public double getPay(int workTime) {
                return workTime * 100;
            }
        },
        HOLIDAY {
            @Override
            public double getPay(int workTime) {
                return workTime * 100;
            }
        };
        
        public abstract double getPay(int workTime);
    }
}
cs




만약 이런 방식이 switch를 사용하는 것보다 더 효율적이다.

switch는 외부 enum 자료형 상수별로 달리 동작하는 코드를 만들어야 할 때는 enum 상수에 switch 문을 적용할 때 좋다.


예를 들어 Operation enum이 다른 누군가가 작성한 자료형이고,
그 각 상수가 나타내는 연산의 반대 연산을 반환하는 메서드를 만들어야 할 때 다음과 같이 하면 용이하다.


1
2
3
4
5
6
7
8
9
pulbic static Operation inverse(Operation op) {
  switch(op) {
      case PLUS: return Operation.MINUS;
      case MINUS: return Operation.PLUS;
      case TIME: return Operation.DIVIDE;
      case DIVIDE: return Operation.TIME;
      defaultthrow new AssertionError("Unknown op: " + op);
   }
}
cs




[결론]
일반적으로 enumb은 int, string 상수와 성능이 비슷하다.
하지만 enum을 사용한 코드는 가독성도 높고, 안전하며 강력하다.

출처 : 조슈아 블로크, 『 Effective Java 2/E』, 이병준 옮김, 인사이트(2014.9.1), 규칙30 인용.

반응형