본문 바로가기

Java

열거형(enums)

728x90

열거형이란?

데이터형의 일종으로, 특정한 값들의 집합을 정의하는 데 사용되며, 이 값들은 열거형 내에서 고유한 상수로 식별됩니다. 열거형 상수를 사용하면 상수의 값이 바뀌어도 기존의 소스를 다시 컴파일하지 않아도 되는 장점이 있습니다.

 

Java에서의 열거형은 '타입에 안전'(컴파일 시 객체의 타입을 체크)한 열거형이기 때문에 두 개의 데이터의 실제 값이 같아도 타입이 다르면 두 데이터는 다르다고 판단합니다. 아래 코드를 보겠습니다.


[참고] - '타입 안전이란?'

프로그래밍에서 주로 사용되는 용어로, 변수나 데이터의 타입(데이터 형식)에 대한 안전성을 나타냅니다. 타입에 안전한 프로그램은 변수나 데이터를 올바른 타입으로 사용하고, 타입 불일치로 인한 오류를 방지합니다.

타입에 안전한 언어 예시로는 Java, C#, Swift 등이 있으며, 이러한 언어는 컴파일 시에 타입 검사를 수행하여 런타임 오류를 방지합니다.

 


public class Animal {
    static final int CAT = 0;
    static final int DOG = 1;
    static final int RABBIT = 2;

    static final int ZERO = 0;
    static final int FIRST = 1;
    static final int SECOND = 2;

    enum Kind {CAT, DOG, RABBIT}
    enum Value {ZERO, FIRST, SECOND}

}


public class Main {
    public static void main(String[] args) {
        System.out.println(Animal.CAT == Animal.ZERO); // true
        System.out.println(Animal.Kind.CAT.equals( Animal.Value.ZERO)); // false
        // Animal.Kind.CAT == Animal.Value.ZERO 컴파일 에러
    }
}

첫 번째 print문은 값으로 두 값을 비교하기 때문에 true이지만, 두 번째 print문은 값뿐만 아니라 타입까지 체크하기 때문에 false로 반환됩니다. Animal.Kind.CAT은 Kind형, Animal.Value.ZERO는 Value형 타입이라 등호(==) 연산 또한 사용 불가능합니다. 


위의 코드에서도 확인하였지만 열거형 상수를 정의하는 방법은 아래와 같습니다.


enum 열거형이름 {상수명1, 상수명2, ...}

 

그리고 Animal 예제에서는 Animal 클래스 내부에 enum 클래스가 선언되어 있어서, 외부에서 호출할 때 static 변수를 참조하는 것과 동일하게 '클래스명. 열거형 이름. 상수명' (e.g. Animal.Kind.CAT)으로 사용하였습니다. 하지만 enum 클래스는 말 그대로 클래스이기 때문에 단독으로 생성이 일반 클래스 없이 생성 가능합니다. 따라서 단독으로 선언된 열거형 상수를  '열거형 이름. 상수명'으로 사용할 수 있습니다.


열거형과 비교 연산자

== 연산자

Animal 예제에서 보았듯이 같은 타입으로 정의된 열거형 상수끼리는 등호 사용이 가능합니다. 

 

부등호 연산자

>, <와 같은 비교연산자는 열거형 상수에서 사용할 수 없습니다. 대신 compareTo()를 사용할 수 있습니다.(같은 타입으로 정의된 열거형 상수끼리만 사용 가능합니다.)  compareTo()는 두 비교 대상이 같으면 0을, 왼쪽이 크면 양수를, 오른쪽이 크면 음수를 반환합니다.

 

아래 코드로 연산자에 대한 내용을 확인해 보겠습니다.


public class Animal {
    enum Kind {CAT, DOG, RABBIT}

    enum Value {ZERO, FIRST, SECOND}

    static void compareExample() {
        System.out.println(Kind.CAT.compareTo(Kind.DOG)); // -1
        System.out.println(Kind.DOG.compareTo(Kind.CAT)); // 1
        //컴파일 에러 System.out.println(Kind.DOG.compareTo(Value.FIRST));
        System.out.println(Value.ZERO == Value.SECOND); // false
    }
}

열거형과 Switch 문

Switch문에서도 열거형 상수를 사용할 수 있습니다. 아래와 같이 사용하면 코드의 가독성이 뛰어나겠죠?


public class Animal {
    enum Kind {CAT, DOG, RABBIT}

    enum Value {ZERO, FIRST, SECOND}

	static void switchExample(Kind kind){
        switch (kind){ // java 17 swtich문
            case CAT -> System.out.println("Meow~");
            case DOG -> System.out.println("Woo Woo!!");
            case RABBIT -> System.out.println("Hop Hop!!");
        }
    }
}


public class Main {
    public static void main(String[] args) {
       	Animal.switchExample(Animal.Kind.CAT);
        Animal.switchExample(Animal.Kind.DOG);
        Animal.switchExample(Animal.Kind.RABBIT);
    }
}

[결과]

Meow~
Woo Woo!!
Hop Hop!!

열거형의 조상 클래스 (java.lang.Enum)

java.lang.Enum을 상속하기 때문에 Enum 클래스에서 제공하는 메서드를 사용할 수 있습니다. 대표적으로 아래와 같은 메서드들이 있습니다.

 

  1. static E values(): 열거형의 모든 상수를 배열에 담아 반환
  2. static E valueOf(String name): name과 일치하는 열거형 상수 반환
  3. T valueOf(Class <T> enumType, String name): enumType형인 열거형에서 name과 일치하는 열거형 상수 반환
  4. Class <E> getDeclaringClass(): 열거형의 Class 객체 반환
  5.  String name(): 열거형 상수의 이름을 문자열로 반환
  6. int ordinal(): 열거형 상수가 정의된 순서 반환 (0부터 시작)

 

각각의 함수를 사용하여 Animal 클래스에 대한 예제 코드를 작성해 보았습니다.


public class Main {
    public static void main(String[] args) {
        for(Animal.Kind kind: Animal.Kind.values()){
            System.out.println("int ordinal(): " + kind.ordinal());
            System.out.println("String name(): "+kind.name());
            System.out.println("getDeclaringClass(): "+kind.getDeclaringClass());
            System.out.println("valueOf(): " + Animal.Kind.valueOf(kind.name()));
            System.out.println("=======================================")
        }
    }
}

[결과]

int ordinal(): 0
String name(): CAT
getDeclaringClass(): class com.example.demo.Animal$Kind
valueOf(): CAT
=======================================
int ordinal(): 1
String name(): DOG
getDeclaringClass(): class com.example.demo.Animal$Kind
valueOf(): DOG
=======================================
int ordinal(): 2
String name(): RABBIT
getDeclaringClass(): class com.example.demo.Animal$Kind
valueOf(): RABBIT
=======================================

열거형 상수 설정하기

위에서 배운 ordinal() 메서드를 사용하면 열거형 상수가 정의된 순서를 반환하였습니다. 이때 개발자가 직접 열거형 상수를 정의하지 않으면 디폴트로 0부터 시작합니다. 하지만 만약 열거형 상수를 직접 정의하고 싶을 때는 어떻게 해야 할까요? 아래 코드처럼 열거형 상수 이름 옆에 원하는 수를 괄호() 안에 함께 적어주면 됩니다.


enum CustomValue {EAT(3), SLEEP(5), WORK(-1)}

하지만 이렇게만 적으면 컴파일 에러가 납니다. 여기에서 enum 클래스의 생성자를 추가로 정의해주어야 합니다. 그리고 마지막 열거형 상수 옆에 ';'을 꼭 붙여주는 것도 잊지 마세요!!


enum CustomValue {EAT(3), SLEEP(5), WORK(-1); // ';' 붙이기!
        private final int value; // final일 필요는 없지만 열거형 '상수'이므로 final로 설정
        CustomValue(int value) { // 접근 제어자는 private (생략됨) --> 외부에서 생성자 접근 불가
            this.value = value;
        }

        public int getValue(){
            return value;
        }
    }

 

필요하다면, 열거형 상수에 여러 값을 지정할 수도 있습니다. 아래 코드를 통해 확인해 보세요!


enum CustomValue {
        EAT(3, "Emm~"), SLEEP(5, "Zzz..."), WORK(-1, "Focus!!");
        private final int value; // final일 필요는 없지만 열거형 '상수'이므로 final로 설정
        private final String sound;

        CustomValue(int value, String sound) {
            this.value = value;
            this.sound = sound;
        }

        public int getValue() {
            return value;
        }

        public String getSound() {
            return sound;
        }
}

public class Main {
    public static void main(String[] args) {
        for(CustomValue e: CustomValue.values()){
            System.out.println(e.ordinal()+". " + e.name()+
                    ": My enum value is "+e.getValue() + 
                    " with sound "+e.getSound());
        }
    }
}

[결과]

0. EAT: My enum value is 3 with sound Emm~
1. SLEEP: My enum value is 5 with sound Zzz...
2. WORK: My enum value is -1 with sound Focus!!

 

열거형과 추상메서드

마지막으로 열거형 상수와 추상메서드를 함께 사용하는 것에 대해서 알아보겠습니다. 열거형 값에 따라 같은 메서드를 다른 방식으로 구현하고 싶을 때 추상메서드를 사용하면 됩니다. 예제 코드를 통해 확인해 보겠습니다.


public enum CustomValue {
    EAT(3, "Emm~") {
        @Override
        int calculate(int weight) {
            return weight * value; // value 변수를 사용할 수도 있고,
        }
    }, SLEEP(5, "Zzz...") {
        @Override
        int calculate(int weight) {
            return weight + getValue(); // getValue()를 사용할 수도 있습니다.
        }
    }, WORK(-1, "Focus!!") {
        @Override
        int calculate(int weight) {
            return 2 * weight * value;
        }
    };
    protected final int value; // 각 상수에서 접근 가능하려면 protected 이어야 합니다.
    private final String sound;

    CustomValue(int value, String sound) {
        this.value = value;
        this.sound = sound;
    }

    public int getValue() {
        return value;
    }

    public String getSound() {
        return sound;
    }

    abstract int calculate(int weight); // 추상 메서드
}


public class Main {
    public static void main(String[] args) {
        System.out.println(CustomValue.EAT.calculate(100));
        System.out.println(CustomValue.SLEEP.calculate(100));
        System.out.println(CustomValue.WORK.calculate(100));
    }
}

 

위에서 보이는 바와 같이 추상메서드를 각 열거형 상수들이 구현함으로써 같은 메서드를 다르게 구현할 수 있습니다.


이상으로 열거형 포스팅을 마치도록 하겠습니다!!

 

 

[참고자료]

남궁 성, [Java의 정석 3rd Edition], 도우출판, 2016, p691~701 

728x90
반응형

'Java' 카테고리의 다른 글

자바에서 애너테이션(Annotation) 직접 정의하기  (0) 2023.10.22
-Xlint 옵션  (0) 2023.10.21
Comparable과 Comparator  (4) 2023.10.20
다형성(Polymorphism)  (2) 2023.10.15
추상클래스(Abstract)과 인터페이스(Interface)  (0) 2023.10.15