https://github.com/whiteship/live-study/issues/11
목차
1. enum 정의하는 방법
2. enum이 제공하는 메소드 (values()와 valueOf())
3. java.lang.Enum
4. EnumSet
1. enum 정의하는 방법
enum의 장점
enum은 서로 관련된 여러 개의 상수를 편리하게 선언하고 사용하기 위해 사용한다.
정수형 열거 패턴과 enum을 비교해보고 enum의 장점에 대해 알아본다.
정수형 열거패턴
public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPPIN = 1;
public static final int APPLE_GRANNY_SMITH = 2;
public static final int ORANGE_NAVEL = 0;
public static final int ORANGE_TEMPLE = 1;
public static final int ORANGE_BLOOD = 2;
정수형 열거 패턴은 아래와 같이 비교할 수 없는 개념의 다른 값을 비교해도 경고를 주지 못한다.
APPLE_FUJI == ORANGE+NAVEL;
위 코드 실행 결과는 true이다. 애초에 할당된 정수값은 상수와 논리적인 연관이 없기 때문에 결과가 true가 아니다. 이런 부적절한 연산에도 컴파일에러를 주지 않기 때문에 후에 런타임에러가 생겼을 때 버그에 대처하기 힘들다.
public class EnumExample {
public static void main(String[] args) {
mixApple(ORANGE_NAVEL); //컴파일 에러 X
}
public static void mixApple(int apple) {
//do something
}
}
또한 정수 열거 패턴은 안전성을 보장할 수 없다. 정수형이기 때문에 위와 같이 APPLE을 보내야할 메서드에 ORANGE를 보내도 컴파일러는 경고 메세지를 출력할 수 없다.
enum
public enum Apple { FUJI, PIPPIN, GRANNY_SMITH}
public enum Orange { NAVEL, TEMPLE, BLOOD }
다음과 같이 enum 클래스를 정의하면 위의 정수형 열거형의 문제점을 해결할 수 있다.
Apple.FUJI == Orange.NAVEL
위와 같은 부적절한 연산도 컴파일 에러로 사전에 방지할 수 있다.
public class EnumEx {
public enum Apple { FUJI, PIPPIN, GRANNY_SMITH}
public enum Orange { NAVEL, TEMPLE, BLOOD }
public static void main(String[] args) {
mixApple(Orange.NAVEL); //컴파일 에러
}
public static void mixApple(Apple apple) {
//do something
}
}
위와 같이 상수를 클래스로 정의해서 관리하면 Apple이 들어가야할 메서드에 Orange가 들어갔을 때 컴파일에러로 경고를 줄 수 있다.
enum 을 정의하는 방법
다음 예제는 자바 오라클 튜토리얼에 올라온 태양계 행성의 enum class이다.
다음 예제를 통해 필드, 생성자, 메서드를 추가하는 법을 배울 수 있다.
public enum Planet {
MERCURY (3.303e+23, 2.4397e6),
VENUS (4.869e+24, 6.0518e6),
EARTH (5.976e+24, 6.37814e6),
MARS (6.421e+23, 3.3972e6),
JUPITER (1.9e+27, 7.1492e7),
SATURN (5.688e+26, 6.0268e7),
URANUS (8.686e+25, 2.5559e7),
NEPTUNE (1.024e+26, 2.4746e7);
private final double mass; // in kilograms
private final double radius; // in meters
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
}
private double mass() { return mass; }
private double radius() { return radius; }
// universal gravitational constant (m3 kg-1 s-2)
public static final double G = 6.67300E-11;
double surfaceGravity() {
return G * mass / (radius * radius);
}
double surfaceWeight(double otherMass) {
return otherMass * surfaceGravity();
}
}
1. 위 예제처럼 enum 내부에서 상수의 이름을 선언할 수 있는데, 마지막에 세미콜론을 붙여야 한다.
2. 과일 예제와 다르게 Planet은 mass와 radius와 같은 값을 가지고 있어서 인스턴스 변수 mass와 radius를 만들었고 생성자를 만들어서 각 상수에 값을 할당했다.
생성자는 묵시적으로 private이기 때문에 외부에서 상수를 추가할 수 없다.
//바이트코드 확인할것
ex) 다음과 같이 상수를 생성할 수 없다.
Planet planet = new Planet(0, 0);
3. 임의의 메서드나 필드를 추가할 수 있다.
인스턴스 변수 외에도 G라는 상수를 확인할 수 있고 이를 활용해서 surfaceWeight()와 같이 그 행성 위에서 무게를 계산하거나,
surfaceGravity()와 같이 각 행성별 중력상수를 계산하는 메서드를 정의할 수 있다.
enum에서 값을 꺼내오는 법
System.out.println(Planet.MERCURY);
System.out.println(Planet.MERCURY.name());
System.out.println(Planet.valueOf("MERCURY");
System.out.println(Enum.valueOf(Planet.class,"MERCURY"));
Ordinal()
ordinal은 정의된 순서이다. 아래 APPLE의 ordinal은 0이고, BANANA의 ordinal은 1이다.
public class EnumExample {
enum Fruit {
APPLE, BANANA
}
public static void main(String[] args) {
System.out.println(Fruit.Apple.ordinal());
System.out.println(Fruit.Banana.ordinal());
if (Fruit.Apple.ordinal() == 0) {
System.out.println("Hello:);
}
}
}
그런데 코드를 수정할 일이 생겨서, Fruit에 아래와 같이 KIWI를 추가하면
public class EnumExample {
enum Fruit {
KIWI, APPLE, BANANA
}
public static void main(String[] args) {
System.out.println(Fruit.Apple.ordinal());
System.out.println(Fruit.Banana.ordinal());
if (Fruit.Apple.ordinal() == 0) {
System.out.println("Hello:);
}
}
}
아래 Fruit.Apple.ordinal() == 0 이 false가 되어 "Hello"가 출력되지 않는다. 코드가 어떻게 수정될지 모르기 때문에 ordinal()을 사용해서 코딩하는 것은 권장하지 않는다. ordinal은 EnumSet이나 EnumMap과 같은 내부 프로그램을 구현할 때 사용하는 것이고, 일반 프로그래머들은 사용할 일이 없다고 생각하면 된다.
비교연산자
열거형 상수 하나하나가 객체라고 언급했다. 그래서 '==' 연산자를 사용해서 주소가 같은지 확인할 수 있지만 '>'나 '<'는 허용하지 않는다. 대신 compareTo를 이용해서 값을 비교할 수 있다. Enum 클래스의 compareTo는 각 상수의 ordinal 값을 기반으로 대소를 비교한다.
2. enum이 제공하는 메서드 (values()와 valueOf())
T[] values()
열거형에 정의된 모든 상수를 얻고 싶을 때 사용하는데, 모든 상수를 배열에 담아 반환한다. 모든 열거형이 가지고 있는데, 컴파일러가 자동으로 추가한다.
public class EnumDemo {
public static void main(String[] args) {
for (Planet planet : Planet.values()) {
System.out.print(planet + " ");
}
}
}
실형 결과 : MERCURY VENUS EARTH MARS JUPITER SATURN URANUS NEPTUNE
Planet.values()의 결과로 Planet 배열을 반환했고, 위에서 언급했듯이 enum 상수를 출력하면 enum 명을 문자열 형태로 출력하는 것을 확인할 수 있다.
T valueOf(Class<T> enumType, String name)
열거형 상수의 이름으로 문자열 상수를 참조해서 해당하는 열거형 객체를 반환한다.
public class EnumDemo {
public static void main(String[] args) {
System.out.println(Planet.valueOf("MERCURY") == Planet.MERCURY);
}
}
실행 결과 : true
Planet.valueOf("MERCURY")에서 "MERCURY"에 해당하는 Planet.MERCURY를 반환하기 때문이다.
3. java.lang.Enum
Class Enum<E extends Enum<E>>
- java.lang.Enum
Type Parameters : E - The enum type subclass
All implemented Interfaces : Serializable, Comparable<E>
모든 enum의 조상이다. enum을 생성할 때, 상속을 명시하지는 않지만 자동으로 Enum을 상속받는다.
생성자
Enum(String name, int ordinal) : 유일한 생성자로 외부에서 호출할 수 없다.
그 밖의 메서드
- int compareTo(E o) : ordinal을 기준으로 값을 객체의 값을 비교한다.
- boolean equals(Object other) : 같은 enum constant라면 true를 반환한다.
- void finalize() : enum은 finalize method를 가질 수 없다.
- Class<E> getDeclaringClass() : 같은 enum 타입의 상수에 해당하는 객체를 반환
- int hashCode() : enum 상수의 hashCode 반환
- String name() : enum 상수의 이름을 반환하는데, 정의된 이름과 정확하게 같다.
- int ordinal() : 정의된 순서를 반환한다.
- String toString() : enum 상수의 이름을 반환하는데, 재정의할 수 있다.
https://docs.oracle.com/javase/7/docs/api/java/lang/Enum.html
4. EnumSet
Class EnumSet<E extends Enum<E>>
enum types을 위한 특수한 형태의 set이다. 모든 element가 single enum type의 상수이다.
Null은 허용되지 않아서, Null을 삽입하면 NullPointerException이 발생한다.
내부적으로 bit vector로 표현돼 있어서 연산이 빠르다. containsAll, retainAll을 포함한 대부분 연산이 constant time으로 실행되고 HashSet보다 빠르다.
다른 collections 구현체와 마찬가지로 synchronized되지 않아서, 여러 thread에서 동시에 접근할 때 혹은 thread 중 하나 이상이 수정하려하면 다음과 같이 외부적으로 동기화해야 한다.
Set<MyEnum> set = Collections.synchronizedSet(EnumSet.noneOf(MyEnum.class));
메서드
EnumSet 클래스는 생성자가 없고, 정적 팩토리 메서드만으로 EnumSet의 구현체를 반환받을 수 있다.
1. allOf(Class<E> elementType) : 모든 상수를 갖는 set을 생성한다.
2. noneof(Class<E> elementType) : elementType을 받을 수 있는 set을 생성한다.
3. range(E from, E to) : 두 특정 포인트 사이의 상수를 포함하는 set을 생성한다.
4. complementOf(EnumSet<E> s) : s에 포함돼있지 않는 차집합 set을 생성한다.
5. of(E e1, E e2, E e3) : 특정 상수를 포함한 set을 생성한다.
6. copyOf(EnumSet<E> s) : 같은 elements를 포함한 set을 생성한다.
public class EnumDemo {
public static void main(String[] args) {
EnumSet<Planet> enumSet1 = EnumSet.allOf(Planet.class);
EnumSet<Planet> enumSet2 = EnumSet.noneOf(Planet.class);
EnumSet<Planet> enumSet3_1 = EnumSet.range(Planet.VENUS, Planet.MARS);
EnumSet<Planet> enumSet3_2 = EnumSet.range(Planet.EARTH, Planet.JUPITER);
EnumSet<Planet> enumSet4 = enumSet3_1.complementOf(enumSet3_2);
EnumSet<Planet> enumSet5 = EnumSet.of(Planet.NEPTUNE, Planet.URANUS);
EnumSet<Planet> enumSet6 = EnumSet.copyOf(enumSet5);
System.out.println(enumSet1);
System.out.println(enumSet2);
System.out.println(enumSet3_1);
System.out.println(enumSet3_2);
System.out.println(enumSet4);
System.out.println(enumSet5);
System.out.println(enumSet6);
}
}
실행 결과
[MERCURY, VENUS, EARTH, MARS, JUPITER, SATURN, URANUS, NEPTUNE]
[]
[VENUS, EARTH, MARS]
[EARTH, MARS, JUPITER]
[MERCURY, VENUS, SATURN, URANUS, NEPTUNE]
[URANUS, NEPTUNE]
[URANUS, NEPTUNE]
Reference
1. 자바의 정석, 3편
2. https://docs.oracle.com/javase/7/docs/api/java/lang/Enum.https://docs.oracle.com/javase/7/docs/api/java/util/EnumSet.htmlhtml
3. https://docs.oracle.com/javase/7/docs/api/java/util/EnumSet.html
4. https://docs.oracle.com/javase/7/docs/api/java/util/EnumMap.html
'자바 > 백기선 자바스터디' 카테고리의 다른 글
[백기선 자바스터디] 람다식 (0) | 2022.12.30 |
---|---|
[백기선 자바스터디] 제네릭 (0) | 2022.10.19 |
[백기선 자바스터디] 예외 처리 (0) | 2022.09.21 |
[백기선 자바스터디] 인터페이스 (0) | 2022.08.10 |
[백기선 자바스터디] 패키지 (0) | 2022.07.25 |