Effective Java : Enum

이수찬·2023년 7월 22일
0

Effective Java

목록 보기
2/2

Enum 클래스
열거형 타입이라고도 부르는 Enum은 JDK 1.5 이후부터 사용할 수 있다.

Enum은 여러 상수로 이루어진 특수 데이터 타입이다.

public enum MemberType {
    USER, ADMIN
}

열거형은 위와 같이 서로 관련있는 상수들을 모아 집합으로 정의한 것이다.


Enum 타입의 특징

  • 열거형을 선언하면, 내부적으로 Enum 클래스형 기반의 새로운 클래스형이 만들어 진다.
  • Enum은 암시적으로 java.lang.Enum 클래스를 상속하는데, 자바에선 클래스의 다중 상속을 허용하지 않으므로 열거형이 다른 열거형이나 클래스를 상속할 수 없다.
  • 열거형은 사실상 final이 달려 있어 클래스가 Enum을 상속받을 수 없다.
    물론 Enum타입도 Interface를 구현할 수 있다.
public enum MemberType {
    USER, ADMIN;
    
    private MemberType() {
    }
}
  • Enum타입의 생성자의 접근제어자는 위와 같이 기본이 private이며 보다 넓은 접근제어자로 명시할 경우 컴파일 에러가 난다.
  • 생성자는 존재하지만, 외부든 내부든 Enum타입을 인스턴스화 할 수 없다.
  • Enum타입의 생성자는 각각의 값에 필드를 할당해주기 위해 존재한다.
    코드로 확인해보자!

Enum 예제 코드

public enum Fruit {

    APPLE(1000) {
        @Override
        public int calculateTotalPrice(int number) {
            return super.price * number;
        }
    },
    BANANA(500) {
        @Override
        public int calculateTotalPrice(int number) {
            return super.price * number;
        }
    },
    CHERRY(100) {
        @Override
        public int calculateTotalPrice(int number) {
            return super.price * number;
        }
    },
    SALEAPPLE(1000) {
        @Override
        public int calculateTotalPrice(int number) {
            int salePercent = 50;
            return (int) ((super.price * number) * salePercent/100);
        }
    };

    private final int price;

    private Fruit(int price) {
        this.price = price;
    }

    abstract public int calculateTotalPrice(int number);
}

Enum 클래스를 위와 같이 만들 수 있다.
어떻게 한 걸까?

아까 위에서 Enum은 암시적으로 java.lang.Enum 클래스를 상속한다고 말했다.
그럼 먼저 java.lang.Enum에 대해 살펴보자.

public abstract class Enum<E extends Enum<E>>
        implements Constable, Comparable<E>, Serializable {

    private final String name;

    public final String name() {
        return name;
    }

    private final int ordinal;

    public final int ordinal() {
        return ordinal;
    }

    protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }
    ...
 }
  • Enum클래스는 추상클래스다.
  • 이를 보면 위의 Fruit도 추상클래스로 정의하여 calculateTotalPrice를 정의하지 않는 채로 둔 후 각 열거형 상수인 과일의 종류들은 Fruit의 익명 클래스로 인스턴스가 된것이다.
  • Enum클래스의 생성자가 protected 인 이유도 만약 위와 같이 익명클래스로 인스턴스가 생성되면 인스턴스의 메소드를 정의하면서 사용하기 때문이다.
  • 결국 Enum 클래스에도 사용할 수 없는 생성자가 존재하는 이유도 원래 Enum클래스 또한 클래스이기 때문이다.

싱글턴과 Enum

Effective Java의 아이템 3 에서 싱글턴을 만드는 세번째 방법으로 Enum을 소개한다.

필드 방식과 정적 팩터리 방식의 싱글턴과 달리 Enum타입의 싱글턴은 여러 장점을 가지고 있다.

  1. 추가 노력없이 직렬화할 수 있다.
    필드와 정적 팩터리 방식의 싱글턴 클래스는 직렬화하려면, 단순히 Serializalbe을 구현하는 것으로는 부족하다. 모든 인스턴스 필드를 transient로 선언하고 readResolve 메소드를 제공해야 한다. 이를 지키지 않으면, 직렬화된 인스턴스를 역직렬화할 때마다 새로운 인스턴스가 만들어진다.

  2. 리플렉션으로 부터 안전하다.
    필드와 정적 팩터리 방식은 private 생성자로 인스턴스 생성을 막으려해도, 리플렉션을 사용하면 인스턴스를 생성할 수 있다.

public class ReflectionTest {

    public static void main(String[] args) {
        try {
            Constructor<Elvis> declaredConstructor = Elvis.class.getDeclaredConstructor();
            declaredConstructor.setAccessible(true);
            Elvis elvis = declaredConstructor.newInstance();
            System.out.println("elvis = " + elvis);
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException |
                 InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}
elvis = com.effectivejava.chapter01.item03.field.Elvis@37a71e93

이를 막으려면 생성자를 2번 이상 호출 시 예외를 던지는 코드를 추가하면 된다.

private static boolean created;

    private Elvis() {
        if (created) {
            throw new UnsupportedOperationException("can't be created by constructor.");
        }

        created = true;
    }

Enum을 사용하면 추가적인 코드 작성없이 리플렉션 문제를 해결해준다.
Enum에서는 리플렉션을 통해서 생성자를 가져오려 해도 예외를 던져 이를 해결한다.

앞으로 싱글턴을 만들 때는 열거타입을 적극 활용해보자!


Enum과 Type-Safe

Type-Safe : 타입을 판별할 수 있어 런타임이 아닌 컴파일 타임에 문제를 잡을 수 있다는 뜻이다.

Enum 타입의 생성자는 private으로 고정되어 있는데 이는 타입 안정성과 관련이 있다.

Enum 타입은 고정된 상수 집합이기 때문에 런타임이 아닌, 컴파일 타임에 모든 값을 알고 있어야 한다. 즉, 다른 곳에서 Enum 타입에 동적으로 접근하여 값을 변경하는 것을 막기 위해 private생성자를 사용하며, 위의 싱글턴 예시에서 봤듯이 리플렉션 공격에도 안전하기 때문에 타입 안정성이 보장되는 것이다.
또한 Enum 타입을 사용하게 되면, 해당 인스턴스는 싱글턴을 보장받기 때문에,==비교가 가능하다.

Enum타입은 기본적으로 MemberType.ADMIN 와 같이 사용하는데, 이를 보면
ADMIN이라는 Enum은 MemberType이라는 타입으로 명시하고 있다.

이는 MemberEntity에서

MemberStatus memberStatus;

와 같이 사용되어 컴파일 타임에 타입 체크를 바로할 수 있다.
-> 타입 캐스팅에서 안전한 것을 알 수 있다!

0개의 댓글