Enum 클래스
열거형 타입이라고도 부르는 Enum은 JDK 1.5 이후부터 사용할 수 있다.
Enum은 여러 상수로 이루어진 특수 데이터 타입이다.
public enum MemberType {
USER, ADMIN
}
열거형은 위와 같이 서로 관련있는 상수들을 모아 집합으로 정의한 것이다.
Enum 타입의 특징
public enum MemberType {
USER, ADMIN;
private MemberType() {
}
}
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
Effective Java의 아이템 3 에서 싱글턴을 만드는 세번째 방법으로 Enum을 소개한다.
필드 방식과 정적 팩터리 방식의 싱글턴과 달리 Enum타입의 싱글턴은 여러 장점을 가지고 있다.
추가 노력없이 직렬화할 수 있다.
필드와 정적 팩터리 방식의 싱글턴 클래스는 직렬화하려면, 단순히 Serializalbe을 구현하는 것으로는 부족하다. 모든 인스턴스 필드를 transient로 선언하고 readResolve 메소드를 제공해야 한다. 이를 지키지 않으면, 직렬화된 인스턴스를 역직렬화할 때마다 새로운 인스턴스가 만들어진다.
리플렉션으로 부터 안전하다.
필드와 정적 팩터리 방식은 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;
와 같이 사용되어 컴파일 타임에 타입 체크를 바로할 수 있다.
-> 타입 캐스팅에서 안전한 것을 알 수 있다!