개발/Java

Java enum (열거형)

Debin 2022. 5. 26. 13:47
반응형

백기선님이 과거에 진행했던 Java 스터디 11주차 스터디 입니다.

enum 정의하는 방법

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

열거형을 정의하는 방법은 아래와 같다.

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

열거형에 정의된 상수를 사용하는 방법은 '열거형이름.상수명'이다.

클래스의 static 변수를 참조하는 것과 동일하다.

enum Direction{
    EAST, SOUTH, WEST, NORTH
}

class Unit{
    int x, y;
    Direction dir;
    
    void init(){
        this.dir = Direction.WEST; //유닛의 방향을 WEST로 초기화한다.
    }
}

열겨형 상수간의 비교에는 ==을 사용할 수 있고, '>', '<'와 같은 비교연산자는 사용할 수 없고 compareTo()는 사용 가능하다.

java.lang.Enum

모든 열거형의 조상은 java.lang.Enum이다.

열겨형 Direction에 정의된 모든 상수를 출력하려면, 다음과 같이 작성한다.

enum Direction{
    EAST, SOUTH, WEST, NORTH
}

public class EnumExample {
    public static void main(String[] args) {
        Direction[] dArr = Direction.values();
        for (Direction direction : dArr) {
            System.out.printf("%s = %d%n", direction.name(), direction.ordinal());
        }
    }
}

출력
direction = EAST
direction = SOUTH
direction = WEST
direction = NORTH

values()

values()는 열거형의 모든 상수를 배열에 담아 반환한다. 이 메서드는 모든 열거형이 가지고 있는 것으로 컴파일러가 자동으로 추가해준다.

 

ordinal()은 모든 열거형의 java.lang.Enum클래스에 정의된 것으로, 열거형 상수가 정의된 순서(0부터 시작)를 정수로 반환한다.

이제 Enum 클래스에서 제공하는 몇 가지 메서드에 대해 알아보자.

 

Class<E> getDeclaringClass()

열거형의 Class객체를 반환한다.

 

String name()

열거형 상수의 이름을 문자열로 반환한다.

 

int ordinal()

열거형 상수가 정의된 순서를 반환한다. (0부터 시작)

 

T valueOf(Class<T> enumType, String name)

지정된 열거형에서 name과 일치하는 열거형 상수를 반환한다.

(참고로 컴파일러가 valueOf(String name)이라는 메서드도 추가로 생성해준다.)

enum Direction{
    EAST, SOUTH, WEST, NORTH
}

public class EnumExample {
    public static void main(String[] args) {
        Direction[] dArr = Direction.values();
        for (Direction d : dArr) {
            System.out.println("d.getDeclaringClass() = " + d.getDeclaringClass());
            System.out.println("d.name() = " + d.name());
            System.out.println("d.ordinal() = " + d.ordinal());
        }

        System.out.println("Direction.valueOf(\"WEST\") = " + Direction.valueOf("WEST"));
        System.out.println("Direction.valueOf(Direction.class,\"EAST\") = " + Direction.valueOf(Direction.class,"EAST"));

    }
}

위 코드에서 출력된 결과물은 아래와 같다.

d.getDeclaringClass() = class happysubin.javapractice.javastudy.week11.Direction
d.name() = EAST
d.ordinal() = 0
d.getDeclaringClass() = class happysubin.javapractice.javastudy.week11.Direction
d.name() = SOUTH
d.ordinal() = 1
d.getDeclaringClass() = class happysubin.javapractice.javastudy.week11.Direction
d.name() = WEST
d.ordinal() = 2
d.getDeclaringClass() = class happysubin.javapractice.javastudy.week11.Direction
d.name() = NORTH
d.ordinal() = 3
Direction.valueOf("WEST") = WEST
Direction.valueOf(Direction.class,"EAST") = EAST

enum에 멤버 추가하기

열거형 상수의 값이 불연속적인 경우에는 이때는 다음과 같이 열거형 상수의 이름 옆에 원하는 값을 괄호와 함께 적으면 된다.

enum Direction{
    EAST(1), SOUTH(5), WEST(3), NORTH(7);
}

그리고 지정된 값을 저장할 수 있는 인스턴스 변수와 생성자를 새로 추가해 주어야 한다.

이 때 주의할 점은, 먼저 열거형 상수를 모두 정의한 다음에 다른 멤버들을 추가해야한다는 것이다.

그리고 열거형 상수의 마지막에 ';'도 잊지 말아야 한다.

enum Direction{
    
    EAST(1), SOUTH(5), WEST(3), NORTH(7);

    private final int value;

    Direction(int value) {  //접근 제어자 private이 생략되었다.
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

열거형 Direction에 새로운 생성자가 추가되었지만, 아래와 같이 열거형의 객체를 생성할 수 없다.

열거형의 생성자는 제어자가 묵시적으로 private이기 때문이다.

Direction d = new Direction(1);

enum에 추상 메서드 추가하기

열거형에 추상 메서드를 선언할 일은 그리 많지 않다고 한다. 가볍게 보고 넘어가자.

enum Transportation{
    BUS(100){
        @Override
        int fare(int distance) {
            return distance * BASIC_FARE;
        }
    },

    TRAIN(150){
        @Override
        int fare(int distance) {
            return distance * BASIC_FARE;
        }
    },
    SHIP(100){
        @Override
        int fare(int distance) {
            return distance * BASIC_FARE;
        }
    },
    AIRPLANE(300){
        @Override
        int fare(int distance) {
            return distance * BASIC_FARE;
        }
    };
    protected  final int BASIC_FARE; //protected로 해야 각 상수에서 접근가능하다.

    Transportation(int basicFare) {
        this.BASIC_FARE = basicFare;
    }

    public int getBASIC_FARE() {
        return BASIC_FARE;
    }

    abstract int fare(int distance); //거리에 따라 계산할 추상 메서드
}

public class EnumExample2 {
    public static void main(String[] args) {
        System.out.println("Transportation.BUS.fare(100) = " + Transportation.BUS.fare(100));
        System.out.println("Transportation.TRAIN.fare(100) = " + Transportation.TRAIN.fare(100));
        System.out.println("Transportation.SHIP.fare(100) = " + Transportation.SHIP.fare(100));
        System.out.println("Transportation.AIRPLANE.fare(100) = " + Transportation.AIRPLANE.fare(100));
    }
}

출력 값은 아래와 같다.

Transportation.BUS.fare(100) = 10000
Transportation.TRAIN.fare(100) = 15000
Transportation.SHIP.fare(100) = 10000
Transportation.AIRPLANE.fare(100) = 30000

예제에서는 오버라이딩한 fare가 동일하지만 언제든지 다르게 바뀔 수 있으므로, 추상 머세드로 선언한 것이다.

정리하면 enum에서 추상 메서드를 선언하면 각 enum 상수가 이 추상 메서드를 오버라이딩해 구현해야 한다.

Enum 이해하기

enum Direction{ EAST, SOUTH, WEST, NORTH }

사실은 열거형 상수 하나하나가 Direction 객체이다. 위의 문장을 클래스로 정의한다면 다음과 같을 것이다.

class Direction{
    static final Direction EAST = new Direction("EAST");
    static final Direction SOUTH = new Direction("SOUTH");
    static final Direction WEST = new Direction("WEST");
    static final Direction NOTRH = new Direction("NORTH");
    
    private String name;

    public Direction(String name) {
        this.name = name;
    }
}

Direction클래스의 static 상수 EAST, SOUTH, WEST, NORTH의 값은 객체의 주소이고,

이 값은 바뀌지 않는 값이므로 == 비교가 가능한 것이다

EnumSet

우선 EnumSet의 상속 구조는 아래 이미지와 같다.

EnumSet

EnumSet을 사용할 경우 몇 가지 중요한 사항을 고려해야 한다.

  • enum 값만 포함할 수 있으며 모든 값이 동일한 enum에 속해야 한다.
  • Null 값을 추가할 수 없으며, Null 값이 들어오면 NullPointerException 예외를 던진다.
  • 스레드 세이프가 아니므로 필요할 경우 외부에서 동기화해야 한다.
  • enum에 선언된 순서에 따라 저장된다.
  • 만약 iterator가 돌면서 컬렉션이 수정되어도,
    iterator에서 작동하는 Fail-Safe 반복기를 사용하므로 ConcurrentModificationException예외가 발생하지 않는다.

비트 벡터를 사용하므로 성능이 매우 우수하다고 한다.

EnumSet을 생성하는 방법은 아래와 같다.

EnumSet.allOf(Color.class);
EnumSet.noneOf(Color.class);

아래와 같은 다양한 메서드가 존재한다.

EnumSet<Color> set = EnumSet.noneOf(Color.class);
set.add(Color.RED);
set.add(Color.YELLOW)

set.contains(Color.RED);

set.forEach(System.out::println);

set.remove(Color.RED);

이상으로 포스팅을 마칩니다. 감사합니다!

 

참고자료

자바의정석 (저자 : 남궁성. 지네릭스, 열거형, 애너테이션)

https://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html

https://www.baeldung.com/java-enumset

반응형