본문 바로가기
시리즈/자바

[JAVA] Enum 열거 타입에 대해 알아보기 ①

by 되고싶은노력가 2024. 10. 29.

들어가며.

 

프리코스 3주차가 시작됐습니다. 지난 2주차 결과물을 보면 상수를 단순히 클래스를 선언하고 static final 로 관리해왔는데요. 이번 요구 사항을 보니 "Java Enum 을 적용하여 프로그램을 구현한다." 라는 문구를 보고 적용해보고자 글을 작성하게 되었습니다. 적용하기에 앞서 Enum 에 대해 알아보고 배운 것을 바탕으로 3주차 과제에 적용해보고자 합니다.


Enum 열거 타입.

 

Enum 은 "Enumeration" 의 약자로 "열거, 목록, 일람표" 라는 뜻을 가지고 있습니다. 즉, 열거형(enum)은 요소, 멤버라 불리는 명명된 값의 집합을 이루는 자료형으로 관련있는 상수 데이터들의 집합이라 생각하면 됩니다.


Enum 이전 상수의 정의.

 

final 상수

가장 먼저 떠오르는 것은 final 제어자를 사용하여 변수를 상수화 하는 것입니다.

제가 2주차 프로젝트에서 상수를 선언하는 방법으로 사용했던 방법인데요. 하지만 이것은 접근제어자와 static final 로 인해 가독성이 좋다고 말할 수 없습니다.

public class InputParser {

    private static final int NAME_LENGTH_MAX = 5;
    private static final String ONLY_NUMBER = "^[1-9]\\d*$";
    private static final String COMMA = ",";
    private static final String SPACE = " ";

    public List<String> toList(final String input) {
        validateInputBlank(input);
        return Arrays.stream(split(input))
                .peek(this::validateToList)
                .toList();
    }
}

 

 

인터페이스 상수

인터페이스는 반드시 추상 메소드만 선언할 수 있는 것이 아닙니다. 인터페이스 내에서도 상수를 선언할 수 있는데, 인터페이스의 멤버는 public static final 속성을 생략할 수 있는 특징을 이용하여 간결하게 작성할 수 있게 됩니다.

public interface NumberGenerator {

    int MAX = 9;
    int MIN = 0;

    int generator();
}

 

하지만 이 패턴은 Effective Java (규칙19) 를 보면 다음과 같은 주요 이유로 안티 패턴으로 간주됩니다.

  1. Implement 할 경우 사용하지 않을 수도 있는 상수를 포함하여 모두 가져오기 때문에 계속 가지고 있어야 한다.
  2. 인터페이스를 구현해 클래스를 만든다는 것은, 해당 클래스의 객체로 어떤 일을 할수 있는지 클라이언트에게 알리는 행위이다. 하지만 상수 인터페이스를 구현한다는 사실은 클라이언트에게 중요한 정보가 아니다. 단지, 혼란만 야기할 뿐이다.
  3. 상수 인터페이스를 Implement 한 클래스에 같은 상수를 가질 경우, 클래스에 정의한 상수가 사용되므로 사용자가 의도한 흐름대로 프로그램이 돌아가지 않을 수 있다.

이에 대한 방안으로 import static 구문 사용을 권장하고 있지만 제 생각으로는 import static 구문 사용도 지양해야 된다고 생각하는데, 정말 사용하려면 오직 상수 인터페이스 용도로만 import static 을 선언해서 사용해야 네임스페이스의 오염을 막을 수 있기 때문입니다.

 

자체 클래스 상수

이에 독립된 고유의 객체로 선언하자는 취지로 자체 클래스 인스턴스화를 이용해 상수처럼 사용하는 기법이 등장하였습니다.

 

자기 자신 객체를 인스턴스화 하고 final static 화 함으로써 고유의 객체를 얻게되어 이를 상수처럼 활용하는 것입니다.

class Dice {
    // 자기 자신 객체를 인스턴스화 하고 final static 화 함으로써 고유의 객체 상수를 얻게 됨
    public final static Dice ONE = new Dice();
    public final static Dice TWO = new Dice();
    public final static Dice THREE = new Dice();
}

 

하지만 가독성이 다시 안 좋아졌고, 무엇보다 이러한 방식은 switch 문에서 사용할 수 없다는 큰 단점이 있습니다, (클린 코드 측면에서는 switch 문은 지양하도록 되어있는데 괜찮지 않을까?) 그래서 이것도 final 상수랑 비슷한 이유로 가독성이 좋지 못하다 할 수 있습니다.

 

 

enum 상수

이러한 문제들 때문에 자바는 아예 상수만을 다루는 enum 타입 클래스를 만들어 배포하였습니다.

 

위의 상수 코드를 enum 으로 바꿔보면 다음과 같습니다.

enum Dice {
	ONE, TWO, THREE, ...
}

 

enum 의 핵심은 이러한 상수를 단순히 정수로 치부하지 말고 객체 지향적으로 객체화해서 관리하자는 취지입니다.


Enum 의 장점.

 

  • 코드가 단순해지며 가독성이 좋아진다.
  • 허용 가능한 값들을 제한하여 type safe 를 제공한다.
  • enum 키워드를 사용하기 때문에 구현 의도가 열거임을 분명하게 나타낼 수 있다.
  • 단순 상수와 비교해 IDE 의 적극적인 지원을 받을 수 있다. (자동완성, 오타검증, 리팩토링 등)
  • 리팩토링 시 변경 범위가 최소화 되기에 유지 보수성이 좋아진다.
  • enum 은 본질적으로 Thread safe 인 싱글톤 객체이므로 싱글톤 클래스를 생성하는데에도 사용된다.

Enum 문법.

 

Enum 선언

열거 타입은 상수 데이터들의 집합이기에 아래와 같이 배열처럼 나열하여 표현할 수 있다.

 

관례적으로 열거 상수는 모두 대문자로 작성하며, 하나가 여러 단어로 구성될 경우 밑줄 (_) 로 연결한다.

enum Dice {
    ONE, 
    TWO, 
    THREE, 
    ...
}

 

 

Enum 참조 방식

Enum 타입 객체도 하나의 데이터 타입이므로 변수를 선언하고 사용할 수 있다.

// 열거타입 변수 = 열거타입.열거상수;
Dice one = Dice.ONE;
DIce two = Dice.TWO;

 

enum 은 referece 타입으로 분류되기에 String 처럼 데이터의 주소값을 저장함으로써 참조 형태를 띄게 됩니다.

 

그렇기 때문에 같은 enum 타입 변수끼리는 둘의 주소를 비교하는  == 연산 결과는 true 가 됩니다.

Dice dice = null; // 참조 타입이기 때문에 null도 저장 가능
dice = Dice.ONE;

// 주소값 비교
System.out.println(dice == Dice.ONE); // true

 

마찬가지로 enum 상수들을 배열로 만들어 저장할 시에도, 각 배열 원소들 마다 참조 주소값들이 저장되어 힙 영역의 상수 데이터드을 가리키게 된다.

// enum Dice 의 모든 상수값들을 배열로 변환
Dice[] dices = Dice.values();

Enum 메소드 종류.

메소드 설명 리턴 타입
name() 열거 객체의 문자열을 리턴 String
ordinal() 열거 객체의 순번(0부터 시작)을 리턴 int
compareTo() 열거 객체를 비교해서 순번 차이를 리턴 int
valueOf(String name) 문자열을 입력받아서 일치하는 열거 객체를 리턴 enum
values() 모든 열거 객체들을 배열로 리턴 enum[]

 

 

name() 메서드

  • 반환되는 문자열은 열거 타입을 정의할 때 사용한 상수 이름과 동일하다.
Dice one = Dice.ONE;

String diceName = w.name();
System.out.println(diceName); // ONE

 

ordinal() 메서드

  • 전체 열거 객체 중 몇 번째 열거 객체인지 순번을 리턴한다.
Dice dice = Dice.ONE;

int diceNum = dice.ordinal();
System.out.println(diceNum) // 0;

 

compareTo() 메서드

  • 매개 값으로 주어진 열거 객체를 비교해서 순번 차이를 리턴한다.
  • 열거 객체가 매개값의 열거 객체보다 순번이 빠르다. (음수 리턴)
  • 열거 객체가 매개값의 열거 객체보다 순번이 늦다. (양수 리턴)
Dice d1 = Dice.ONE; // 0
Dice d2 = Dice.TWO; // 1

// d1 이 d2 보다 순번이 빠르므로 음수 리턴
int compare1 = d1.compareTo(d2); // d2 기준으로 d1 위치 (1 에서 0이 되기 위한 값)
System.out.println(compare1); // -1

// d2 이 d1 보다 순번이 느리므로 양수 리턴
int compare2 = d2.compareTo(d1); // d1 기준으로 d2 위치 (0 에서 1이 되기 위한 값)
System.out.println(compare1); // 1

 

valueOf() 메서드

  • 매개값으로 주어지는 문자열과 동일한 문자열을 가지는 열거 객체를 리턴한다.
Dice dice = Dice.valueOf("ONE");
System.out.println(dice); // ONE;

 

values() 메서드

  • 열거 타입의 모든 열거 객체들을 배열로 만들어 리턴한다.
Dice[] dices = Dice.values();

System.out.println(Arrays.toString(dice)); // [ONE, TWO, THREE, ...]

 

java.lang.Enum 클래스

모든 클래스가 Object 클래스를 자동 상속하는 것처럼 Enum 클래스도 Object 클래스를 자동 상속하기 때문에 Object 클래스의 메서드를 그대로 사용할 수 있다.

 

단, Enum 클래스는 Object 클래스 중 다음 4개의 메서드는 오버라이딩 하지 못하도록 메서드를 final 화 하여 막아놓았는데 그 이유는 enum 은 고유한 상수이기 때문에 오버라이딩해서 마음대로 바꿔버리면 고유성이 깨지기 때문이다.

  • clone() : 객체를 복제하기 위한 메서드이지만 enum 클래스에서 사용 불가하다.
  • finalize() : GC가 발생할 때 처리하기 위한 메서드
  • hashCode() : int 타입의 해시 코드 값을 리턴하는 메서드
  • equals() : 두 개의 객체가 동일한지 확인하는 메서드

참고자료

Inpa Dev 자바 Enum 열거형 타입 문법 & 응용 정리

https://camel-context.tistory.com/58