Java : Enum

unchapterd·2021년 11월 1일
0

Java

목록 보기
11/19
post-thumbnail

Enum ( Enumeration )

본 문서는 2021년 12월 19일에 기록되었다.

과거 Life Folio 프로젝트를 하면서 카테고리와 같은 정보 타입에 int 값을 할당하는 것이 개발자 입장에서 직관적이지 않으며, 원하는 정보를 얻기 위해 switch 문을 써야 하는 등의 문제가 있음을 체감하였다.

과거 프로젝트를 진행하면서 다음과 같은 기능에 대한 고민을 한 적이 있다.
카테고리와 같은 데이터를 어떠한 데이터 타입으로 받아야 하는가?

위 고민에 대해서 생각난 것은 아래의 세 가지였다.

  1. int 값으로 저장하고 이를 switch 문으로 변환
  2. String 값으로 저장하고 비교시에 .eqauls() 로 비교
  3. Enum 사용

하지만 당시에는 Enum 을 모를 때였고
1번과 2번의 방법이 비즈니스 로직에서 어떤 장단점을 가질 것인가에 대한 고민을 했다.

  1. int
    1.a. 장점 | String 비교 보다 int 비교가 빠르다
    1.b. 단점 | 해당 int 값이 어떤 String 을 의미하는지 알려면 switch 문등을 통해 변환하는 절차가 필요하다. 따라서 비직관적이다
  2. String
    2.a. 장점 | 직관적이다.
    2.b. 단점 | Java 에서는 .equals() 로 비교해야 하고 String 자체의 비교가 int 비교보다 오래 걸린다.

따라서 나는 이러한 경우에 문제를 해결해줄 수 있는 방법이 어쩌면 Enum 이지 않을까? 라는 생각을 하게 되었고 이에 대해서 깊게 공부하게 되었다.

참고로, Enum 은 jdk 1.5 이후에 추가되었다.


정의

열거형은 서로 관련된 상수를 편리하게 선언하기 위한 것이다.
따라서 여러 상수를 정의할 때 사용하면 유용하다.

C 언어와 Java 의 Enum 은 약간 다른데 그 차이는 다음과 같다.

  1. C 언어에서는 Enum 이 갖는 값만을 비교한다.
  2. Java 언어에서는 Enum 이 갖는 값과 타입도 비교한다.

따라서 Essential of Java 에서는 이러한 이유로 Java의 enum 이 더 향상된 것이라고 표현하였으며, 더하여 Typesafe Enum 이라며, 타입에 대한 안정성이 확보된다 라고도 표현하였다.

상수의 값이 바뀌면,
해당 상수를 참조하는 모든 소스를 다시 컴파일 하여야 한다.
하지만 열거형 상수를 사용하면 기존의 소스를 다시 컴파일 하지 않아도 된다.

위 부분을 이해할 수 없어서 Okky.kr 커뮤니티에 질문을 남기고 이에 대한 답을 받게 되었다. (너무 감사합니다...)

Contibutes | Dierslair

열거형이 상수형보다 좋은 이유?

Effective Java 아이템 34 에는 다음과 같은 내용이 있다.
열거형을 선언해놓고 하나를 지우면 어떻게 될까?
다음과 같은 경우의 수가 존재한다.

  1. 참조한 경우 | 에러가 발생한다.
  2. 참조하지 않은 경우 | 에러가 발생하지 않는다.

그렇다면 상수형을 선언해놓고 하나를 지우면 어떻게 될까?

  1. 참조한 경우 | 에러가 발생하지 않는다.
  2. 참조하지 않은 경우 | 에러가 발생하지 않는다.

선언 및 생성

선언 및 생성에서는 열거형 타입의 변수를 선언하고
이를 기반으로 인스턴스 변수 생성에 대한 내용을 포함하고 있다.

또한 다음과 같은 구성을 통해서 Enum 에 대해서 설명할 것이다.

  1. 열거형 선언 및 생성에 대한 기본사항
  2. 열거형 조상 java.lang.Enum 에 대한 기본사항
  3. 열거형 활용

열거형 선언 및 생성에 대한 기본사항

열거형 선언 및 생성에 대한 기본사항은 다음과 같은 방법을 사용한다.
열거형 변수를 만들고 이를 통해 열거형 변수 타입 인스턴스 변수를 만든다.

그러나,
인스턴스 변수라는 개념이 낯설거나 Enum 을 처음 보는 사람들은 다음의 코드를 먼저 보고 내용을 읽어 가는 것을 권고한다.

enum Category { 스릴러, 로멘스, 드라마, 기타 }

class Movie {
   int serialNumber;
   Category category;
   
   void init(){
      category=Category.드라마;
   }
   void ifTest(){
     if(category==Category.드라마){ // ok
     }else if(category.equals(Category.드라마)){ // 컴파일 에러
     }else if(category > Category.드라마) { // 컴파일 에러
     }else if(category.compareTo(Category.드라마)){ // ok
     }
   }
   void switchTest() {
     switch(category){
     case Category.스릴러: break; // 컴파일 에러
     case 스릴러: break; // ok
     case 로멘스: break; // ok
     case 드라마: break; // ok
     case 기타: break; // ok
     }
   }
}

위에도 주석처리르 통해 적었지만, Enum 이 가지는 특이한 점들은 다음과 같다.

  1. Enum 은 외관상 String 과 같은 Wrapper Class 와 같으나,
    동일 값 비교 시에는 .equals() 메서드가 아닌 == 연산자를 사용하며
    대소 값 비교 시에는 >, < 연산자가 아닌 compareTo() 메서드를 사용한다.

  2. 미리 만들어준 Enum 타입 변수로 만든 인스턴스 변수는
    switch 문 안에 들어가면 EnumName.열거형_상수명 이 아니라 열거형_상수명 의 형태로 사용된다.
    ex)
    switch(dir){
    case Direction.EAST: // 에러
    case EAST; // ok
    }

열거형 조상 java.lang.Enum 에 대한 기본사항

java.lang.Enum 은 기본적으로 여러 메서드들을 가지고 있고 이들 중 다음의 것들이 있다.

  1. Class<E> getDeclaringClass() | 열거형 타입 매개변수를 가지는 Class 객체를 반환
  2. String name() | 열거형 상수의 이름을 문자열로 반환
  3. int ordinal() | 열거형 상수가 정의된 순서를 반환(0부터 시작)
  4. T valueOf(Class<T> enumTpye, String name) | 지정된 열거형에서 name 과 일치하는 열거형 상수를 반환한다.

또한 컴파일러가 자동으로 추가해주는 다음과 같은 메서드도 있다.

  1. Enum.values() | 열거형의 모든 상수를 배열에 담아서 반환한다.
  2. Enum.valueOf(String name) | 아래 코드 참고
// 1. Enum.values();
enum Category={ 스릴러, 로멘스, 드라마, 기타 };

Category categories[]=Category.values();

for(Category element : categories){
   System.out.printf("%s = %d%n", element.name(), element.ordinal());
}

// 2. Enum.valueOf(String name);
Category category=Category.valueOf("스릴러");

System.out.print(category); // 스릴러
System.out.print(Category.스릴러 == Category.valueOf("스릴러")); // true

열거형 활용 1 | 멤버변수 추가

위 두 Enum 선언 및 생성, java.lang.Enum 에 대한 기본 사항을 통해 Enum 이라는 것에 대한 감을 잡았을 것이다.
그러나 위에서 언급한 메서드 중 int ordinal() 이라는 메서드에는 주의할 점이 있는데 그것들은 다음과 같다.

  1. ordinal() 은 열거형 상수가 정의된 순서를 반환하지만, 이를 외부에서 상수로써 사용해서는 안된다.
  2. ordinal() 을 사용하기 이전에, 열거형 상수의 값이 불연속적이라면 열거형 상수의 이름 옆에 원하는 값을 적어야 한다.
enum Category {
   스릴러(1), 로멘스(2), 드라마(3), 기타(4);
   
   private final int value;
   
   // enum 의 생성자는 묵시적으로 private 이므로 이를 호출할 수 없다!!
   Category(int value){
      this.value=vlaue;
   }
   
   public int getValue { return value; }
}
  1. 필요하다면 열거형 상수에 여러 가지 값을 넣을 수도 있다. 그러나 이에 따라 후속 처리가 있음을 잊지 말자.
enum Cateogry {
   스릴러(1,"아무거나"), 로멘스(2,"이것저것"), 드라마(3,"끄아악"), 기타(4,"음...");
   
   private fianl int value;
   private final String text;
   
   // enum 의 생성자는 묵시적으로 private 이므로 이를 호출할 수 없다!!
   Category(int value, String text) {
      this.value=value;
      this.text=text;
   }
   
   public int getValue() { return this.value }
   public String getText() { return this.text }
}

열거형 활용 2 | 추상메서드 추가

경우에 따라서 열거형에 추상 메서드를 추가하여 기능을 보완할 필요가 있다.
Essential of Java 에서는 대중교통의 종류와 이에 대응되는 요금 체계에 대해서 언급하였다.
버스의 기본요금이 1000원이면 버스를 타고 이동하는 거리에 따라서 추가 요금이 붙는 것들에 대한 것이다.

enum Transportation {
   Bus(100) {
      int cost(int distance) {
         return distance*BASIC_COST;
      } // 추상 메서드의 구체화
   },
   Train(150) {
      int coust(int distance) {
         return distance*BASIC_COST;
      } // 추상 메서드의 구체화
   };
   
   abstract int count(int distance); // 거리에 따른 요금을 계산하는 추상 메서드
   
   protected final int BASIC_COST;
   
   Transportation(int basicCost){
      this.BASIC_COST = basicCost;
   }
   
   public int getBaiscCost() {
      return BASIC_COST;
   }
}

다만,
열거형 필드 안에 abstract method 를 만들면 각 열거형 상수의 필드에 method 를 구현해야 하는 것인지는 잘 모르겠다.
분명한 인과관계가 있을 것인데, 이 부분에 대한 설명은 Essential of Java 699p~700p 를 보고 참고하도록 하자.


이해

열거형 Categories 를 클래스 Categories 를 통해 표현해보고
열거형의 내부구조가 어떤 식으로 생겼는지 이해해보고 전술한 Enum 만의 특별한 특징들이 왜 가능한지 생각해보자

enum Categories { 스릴러, 로멘스, 드라마, 기타 }

class Categories {
   static final Direction 스릴러=new Direction("스릴러");
   static final Direction 로멘스=new Direction("로멘스");
   static final Direction 드라마=new Direction("드라마");
   static final Direction 기타=new Direction("기타");
   
   private String name;
   private Categories(String name) {
      this.name=name;
   }
}
profile
문제없는 기록

0개의 댓글