Enum 은 쓰지 않았다. 쓸 줄도 몰랐고 쓸 필요성도 느끼지 못했다. 하지만 프로젝트의 규모가 커지고 좀 더 간결하고 가독성 좋은 코드를 필요로 하는 경우 Enum 을 아는 것과 알지 못하는 것에는 차이가 있더라.


Enum 누군가에게 설명할 수 없으면 나는 Enum에 대해 잘 모르는 것이다.


Enum 을 알기에 앞서서, 나는 상수 정의를 단 한가지 방법으로 했었다. (1) 번 밖에 사용할 줄 몰랐다. 그 외에 다른 상수 정의 방법도 살펴보자.


(1) 클래스 내부에 static final 로 해당 필드를 선언 및 초기화

내가 이렇게 하는 이유는 간단하다. static 으로 메모리에 한 번만 할당시키고 인스턴스 변수가 아닌 클래스 변수로 만들어 주었다. 그리고 final 을 통해서 해당 상수 값의 변경을 막기 위함이다. 이렇게 하면 상수형 값을 만들경우 네이밍은 무조건 대문자이며 단어 사이에는 항상 "_" (underbar) 가 들어가 있어야 한다. ex) ORALCE_USER


그리고 중복되는 문제들에 대해서는 네임스페이스 기법을 통해서 "_" 를 붙여가면서 상수들을 구별해줄 수 있다. 예를 들면 같은 "배" 라 할 지라도 바다에 있는 "배(Boat)" 인지 혹은 사람 몸의 부위인 "배(Belly)" 인지 구분해주어야 하기 때문이다. 


하지만 이러한 내용들이 많아지면 지저분해지기 때문에 인터페이스를 통해 극복할 수 있다.


(2) 인터페이스를 이용

인터페이스 내부에 필드를 선언하면 자동으로 public static final 이 같이 포함되기 때문에 생략이 가능하다. 컴파일러가 아무런 오류를 일으키지 않는다. 하지만 인터페이스를 이용한다고 하더라도 여전히 각각의 인터페이스 내부에는 값이 상수로 선언되어있기 때문에 중간에 값이 교체되면 문제가 된다. 아래의 예시는 생활코딩에서 들고왔습니다.


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
interface FRUIT{
    int APPLE=1, PEACH=2, BANANA=3;
}
interface COMPANY{
    int GOOGLE=1, APPLE=2, ORACLE=3;
}
 
public class Main {
     
    public static void main(String[] args) {
        
        /**ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ**/
        int type = COMPANY.APPLE;
        /**ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ**/
        
        switch(type){
            case FRUIT.APPLE:
                System.out.println(57+" kcal");
                break;
            case FRUIT.PEACH:
                System.out.println(34+" kcal");
                break;
            case FRUIT.BANANA:
                System.out.println(93+" kcal");
                break;
        }
    }
}
cs


문제가 되는 코드이다. 13번째 줄의 type으로 받는 값이 결국에는 기본형이기 때문에 스위치문을 통해서 비교하는 값이 int 형이다. 이러한 오류들은 컴파일러가 찾아주는 것이 아니기 때문에 이후에 프로젝트의 규모가 커지면 문제가 될 것이다. 하나 더 살펴보자. 


각각의 인터페이스에 대해서 동일한 상수형을 반환하기 때문에 조금 달리 타입을 바꾸어줄 필요가 있었다. 


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
class Fruit{
    public static final Fruit APPLE  = new Fruit();
    public static final Fruit PEACH  = new Fruit();
    public static final Fruit BANANA = new Fruit();
}
class Company{
    public static final Company GOOGLE = new Company();
    public static final Company APPLE = new Company();
    public static final Company ORACLE = new Company();
}
 
public class Main {
     
    public static void main(String[] args) {
 
        /***********************************************/
        if(Fruit.APPLE == Company.APPLE){
        /***********************************************/
 
            System.out.println("과일 애플과 회사 애플이 같다.");
        }
        else
            System.out.println("과일 애플과 회사 애플은 다르다.");
    }
}
cs


17번째 줄을 살펴보면 위의 해당 내용은 컴파일 에러를 일으킨다. 에러의 내용은 "Incompatible operand types Fruit and Company" 인데, 호환되지 않는 피연산자 유형을 의미한다. 서로간의 타입이 호환되지 않기 때문에 컴파일러가 런타임 이전에 오류를 일으킨 것이다. 이런 오류는 실행하기 이전에 확인할 수 있기 때문에 즉시 확인하고 수정할 수 있다.



Enum 등장


Enum(Enumeration)

우리말로 열거형이라고 하며, 서로 연관된 상수들의 집합을 의미한다. 앞선 (1)과 (2) 에서 예시로 든 Fruit 와 Company 가 열거인 것이다. enum 은 class와 interface 와 동급의 형식을 가지는 단위이지만 enum 은 사실 class이다. 단지 클래스와 구분하기 위해 enum을 이용하는 것이며 앞선 Fruit Class 를 Fruit Enum 처럼 사용할 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
enum Fruit{
    APPLE, PEACH, BANANA;
}
 
/**ㅡㅡㅡㅡㅡ enum Fruit 과 class Fruit 내용 동일 ㅡㅡㅡㅡㅡ**/
 
class Fruit{
    public static final Fruit APPLE  = new Fruit();
    public static final Fruit PEACH  = new Fruit();
    public static final Fruit BANANA = new Fruit();
    
    private Fruit(){}
}
cs


둘의 내용은 동일하다. 하지만 하나는 클래스 형태이고 하나는 열거형 형태이다. 


클래스의 생성자를 보면 접근지정자가 private 인데 해당 클래스는 다른 용도로 사용을 금지하는 것이다. 생성자의 접근지정자가 private 이기 때문에 Fruit 를 직접 생성 가능하다. 


enum type은 결국 런타임이 아닌 컴파일타임에 모든 값을 알고 있어야 한다. 즉 다른 패키지나 클래스에서 enum 타입에 접근해서 동적으로 어떤 값을 줄 수 없다. 클라이언트에서 enum을 new 연산자를 통해서 인스턴스를 생성할 수 없으며 상속받을 수도 없다. 따라서 클라이언트의 관점에서는 enum 의 인스턴스는 존재하지 않으며 enum 상수는 존재하고 있는 셈이다.


enum type 은 인스턴스 생성을 제어하며, 싱글톤을 일반화한다.


결과적으로 앞선 컴파일 에러 (Fruit.APPLE == Company.APPLE) 의 문제가 enum type 에도 그대로 적용되어 컴파일 시점에서 이를 미리 인지하고 예방할 수 있다.


다음 포스팅에는 열거형 타입(enum type) 으로 할 수 있는 것들을 알아보려고 한다.



Posted by doubler
,