기록하자..
Enum | 백기선님 LIVE-STUDY 본문
Enum
학습할 것
- enum 정의하는 방법
- enum이 제공하는 메소드 (values()와 valueOf())
- java.lang.Enum
- EnumSet
Enum 이란?
Enum은 서로 관련된 상수를 편리하게 선언하기 위한 것이다. Enum을 통해 변수를 미리 정의된 상수가 될 수 있도록 해준다.
Enum 정의하는 방법
Enum을 정의하는 방법을 알아보자.
enum 열거형 이름 {상수명1, 상수명2, ...}
enum Direction { EAST, WEST, SOUTH, NORTH }
위와 같이 간단하게 {} 안에 상수의 이름을 나열하면 된다.
위와 같이 Enum을 선언했다면 이제 해당 타입을 사용할 수 있다.
Direction curDir;
Direction prevDir;
Direction curDir = Direction.EAST;
열거 타입 변수에는 null을 지정할 수 있는데 이는 Enum이 참조 타입이기 때문이다.
열거 타입 Direction의 경우 EAST 부터 NORTH까지 4개의 Direction 객체로 생성이 된다. 그리고 메서드 영역에 생성된 열거 상수가 해당 Direction 객체를 각각 참조하게 된다.

열거 타입 변수 curDir는 스택 영역에 생성되고 curDir에 저장되는 값은 Direction.EAST 열거 상수가 참조하는 객체의 번지이다. 따라서 Direction.EAST와 curDir는 같은 Direction 객체를 참조하게 된다.
그렇기에 다음은 true가 된다.
curDir == Direction.EAST
Enum의 등장 배경
Enum이 사용되기 전에는 다음과 같이 상수를 클래스로 정의하였다.
public class EnumEx {
public static final int APPLE = 1;
public static final int BANANA = 2;
public static final int COCONUT = 3;
public static void main(String[] args) {
iint type = APPLE;
switch (type) {
case APPLE:
System.out.println("1000원");
break;
case BANANA:
System.out.println("2000원");
break;
case COCONUT:
System.out.println("3000원");
break;
}
}
}
위 코드는 APPLE을 1, BANANA를 2, COCONUT을 3으로 아무 의미 없이 정의하고 있다. 정수들과 아무 관련이 없는대도 말이다.
이렇게 상수를 정의할 때 발생할 수 있는 문제는 이름이 충돌할 수 있는 상황이 발생한다는 것이다.
public class EnumEx {
public static final int APPLE = 1;
public static final int BANANA = 2;
public static final int COCONUT = 3;
// ... ...
public static final int APPLE = 1;
public static final int GOOGLE = 2;
public static final int FACEBOOK = 3;
}
이렇게 IT 회사를 상수로 나타내려 했지만 위의 APPLE 때문에 컴파일 에러가 발생하게 된다.
아래와 같이 이름을 구분해주거나
public class EnumEx {
public static final int FRUIT_APPLE = 1;
public static final int FRUIT_BANANA = 2;
public static final int FRUIT_COCONUT = 3;
// ... ...
public static final int COMPANY_APPLE = 1;
public static final int COMPANY_GOOGLE = 2;
public static final int COMPANY_FACEBOOK = 3;
}
인터페이스를 통해 구분할 수 있다.
interface Fruit {
int APPLE = 1, PEACH = 2, BANANA = 3;
}
interface Company {
int APPLE = 1, GOOGLE = 2, FACEBOOK = 3;
}
하지만 인터페이스로 상수를 관리하는 것은 안티 패턴이다. 인터페이스는 규약을 정의하기 위해 만든 것이기 떄문에 이렇게 사용하는 것은 옳지 않다.
마지막으로 Friut의 APPLE과 COMPANY의 APPLE은 모두 정수형이기 때문에 다음과 같은 논리적 오류가 발생할 수 있다.
if (Fruit.APPLE == Company.APPLE) {
// ...
}
이런 문제를 해결하기 위해 서로 다른 인스턴스로 만들어 처음부터 비교를 못하도록 막아주면 된다.
class Fruit {
public static final Fruit APPLE = new Fruit();
public static final Fruit PEACH = new Fruit();
public static final Fruit BANANA = new Fruit();
}
class Company {
public static final Company APPLE = new Company();
public static final Company GOOGLE = new Company();
public static final Company FACEBOOK = new Company();
}
이렇게 되면 위의 문제들이 모두 해결되는데, 이것이 Enum이다.
- 상수와 리터럴의 논리적인 연관이 없다.
- 서로 다른 개념끼리 이름이 충돌할 수 있다.
- 서로 다른 개념인데도 코드가 비교 가능하다.
enum이 제공하는 메소드 (values()와 valueOf())
모든 enum은 java.lang.Enum 클래스를 부모 클래스로 가진다.
| 메소드 | 설명 |
|---|---|
| Class getDeclaringClass() | 열거형의 Class객체를 반환 |
| String name() | 열거형 상수의 이름을 문자열로 반환 |
| int ordinal() | 열거형 상수가 정의된 순서를 반환(0부터 시작) |
| T valueOf(Class enumType, String name) | 지정된 열거형에서 name과 일치하는 열거형 상수를 반환 |
| compareTo(E o) | 지정된 객체보다 작은 경우 음의 정수, 동일한 경우 0, 크면 양의정수를 반환 |
toString() 과 name() 의 차이
name()은 final로 선언되어 있기 때문에 오버라이딩이 불가능하다.
하지만 toString()은 일반적인 Object 클래스의 메서드이기 때문에 오버라이딩이 가능하다.
values()
values() 메서드는 enum 타입의 모든 열거 객체들을 배열로 만들어서 리턴한다.
Direction directions = Direction.values();
for (Direction dir : directions) {
System.out.println(dir);
}
valueOf()
valueOf() 메서드는 매개값으로 주어지는 문자열과 동일한 문자열을 가지는 열거 객체를 리턴해준다.
Direction dir = Direction.valueOf("SOUTH");
java.lang.Enum
위에서 언급했듯이 java.lang.Enum 클래스는 모든 열거타입이 부모 클래스로 가지는 추상 클래스이다.
public abstract class Enum<E extends Enum<E>>
implements 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;
}
public String toString() {
return name;
}
public final boolean equals(Object other) {
return this==other;
}
public final int hashCode() {
return super.hashCode();
}
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
public final int compareTo(E o) {
Enum<?> other = (Enum<?>)o;
Enum<E> self = this;
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}
@SuppressWarnings("unchecked")
public final Class<E> getDeclaringClass() {
Class<?> clazz = getClass();
Class<?> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
}
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
@SuppressWarnings("deprecation")
protected final void finalize() { }
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can't deserialize enum");
}
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("can't deserialize enum");
}
}
toString() 을 제외한 대부분의 메서드들은 final로 선언되어 있어 별도의 오버라이딩을 할 수 없다.
EnumSet
EnumSet은 Set 인터페이스를 기반으로 열거형 타입으로 저장해 놓은 요소들을 빠르게 찾을 수 있도록 도와준다.

위와 같이 EnumSet은 Set 인터페이스를 구현하고 있는 AbstractSet을 상속 받는다.Set인터페이스가 Collection 인터페이스에서 제공하는 대부분의 기능을 포함하고 있지만 EnumSet은 대부분의 메서드들을 오버라이딩하고 있다.
우리가 EnumSet을 사용하기 전에 알아둬야할 것들이 있다.
- EnumSet은 오로지
enum값들만 가지고 있어야 하고 이 값들은 모두 같은 enum 타입이어야 한다. - null 값을 더할 수 없다. 만약 더할려 한다면 NullPointerException이 발생한다.
- thread-safe 하지 않다.
- 상수는 열거형에 선언된 순서에 따라 저장된다.
- 복사본에서는 Fail-Safe 방식의 Iterator를 사용한다. 동작중 컬렉션이 수정되어도 작업을 중단하지 않고 진행한다. 즉, ConcurrentModificationException이 발생하지 않는다.
Fail-Safe는 어떤 Collection에 대해 순회를 할 때 실제 Collection의 클론을 만들어서 해당 클론은 순회한다.
만약 iterator가 생성된 후 원본에 어떠한 변화가 발생해도 계속헤서 순회를 이어나가는 방식이다.
이와 반대로 Fail-Fast는 원본 Collection에 대해 직접 순회를 실시한다.
Java의 컬렉션은 modCount라는 내부의 Counter를 운용하는데, Collection에 어떤 element가 추가되거나 삭제 될 때 마다 modCount값이 변경된다. 이제 iterator의 next()를 호출할 때마다 이 modCount값을 현재 modCount값과 비교해서 다르다면 ConcurrentModificationException이 발생한다.
public class EnumSetEx {
public enum Direction { EAST, WEST, SOUTH, NORTH }
public static void main(String[] args) {
EnumSet<Direction> enumSet = EnumSet.allOf(Direction.class);
// enumSet 출력
System.out.println(enumSet);
// EnumSet 의 내용을 비움
EnumSet<Direction> emptySet = EnumSet.noneOf(Direction.class);
System.out.println(emptySet);
// EnumSet 의 매개변수로 들어온 내용을 새로운 EnumSet 으로 반환
EnumSet<Direction> enumSet1 = EnumSet.of(Direction.EAST, Direction.WEST);
System.out.println(enumSet1);
// EnumSet 의 매개변수로 들어온 EnumSet 을 제외한 열거형으로 새로운 EnumSet을 반환
EnumSet<Direction> enumSet2 = EnumSet.complementOf(enumSet1);
System.out.println(enumSet2);
// 인덱스 범위만큼 새로운 EnumSet 을 반환
EnumSet<Direction> rangeEnumSet = EnumSet.range(Direction.EAST, Direction.SOUTH);
System.out.println(rangeEnumSet);
}
}
참고 자료
'자바' 카테고리의 다른 글
| I/O | 백기선님 LIVE-STUDY (0) | 2022.08.06 |
|---|---|
| Annotation | 백기선님 LIVE-STUDY (0) | 2022.08.06 |
| 멀티스레드 프로그래밍 | 백기선님 LIVE-STUDY (0) | 2022.05.31 |
| 예외처리 | 백기선님 LIVE-STUDY (0) | 2022.05.31 |
| 인터페이스 | 백기선님 LIVE-STUDY (0) | 2022.05.31 |