개발/Java

Java enum (열거형)

Debin 2022. 5. 26.
반응형

백기선님이 과거에 진행했던 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

반응형

댓글