///
Search

Enum

태그
JAVA
Enum? 열거형은 서로 관련된 상수를 편리하게 선언하기 위한 것으로 상수를 여러 개 정의할 때 사용한다. 열거형은 상수 정의 이후 정의된 것 이외의 값은 허용하지 않는다. 오라클 튜토리얼에는 열거형을 이렇게 설명하고 있다. 열거형 은 변수가 미리 정의된 상수 집합이 될 수 있도록 하는 특수 데이터 유형입니다 . 변수는 변수에 대해 미리 정의된 값 중 하나와 같아야 합니다. 즉, 열거형은 상수 집합을 만들때 사용한다는 것이다. 예를 들자면 뭐가 있을까? 바뀌지 않지만 집합의 형태를 가지고 있는 우리 일상 속의 것들이? 요일, 달력의 날짜, 국가명, RGB값? 이런것도 될 수 있지 않을까?
public enum Day { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY } ... public class EnumTest { Day day; public EnumTest(Day day) { this.day = day; } public void tellItLikeItIs() { switch (day) { case MONDAY: System.out.println("Mondays are bad."); break; case FRIDAY: System.out.println("Fridays are better."); break; case SATURDAY: case SUNDAY: System.out.println("Weekends are best."); break; default: System.out.println("Midweek days are so-so."); break; } } public static void main(String[] args) { EnumTest firstDay = new EnumTest(Day.MONDAY); firstDay.tellItLikeItIs(); EnumTest thirdDay = new EnumTest(Day.WEDNESDAY); thirdDay.tellItLikeItIs(); EnumTest fifthDay = new EnumTest(Day.FRIDAY); fifthDay.tellItLikeItIs(); EnumTest sixthDay = new EnumTest(Day.SATURDAY); sixthDay.tellItLikeItIs(); EnumTest seventhDay = new EnumTest(Day.SUNDAY); seventhDay.tellItLikeItIs(); } } CONSOLE RESULT Mondays are bad. Midweek days are so-so. Fridays are better. Weekends are best. Weekends are best
Java
복사
Enum 예시 출처 : oracle tutorial

Enum의 정의 방법

열거형 내부에는 메서드, 기타 필드가 포함될 수 있다. 단, 컴파일러는 열거형을 만들때 values(), valueOf() 추가해서 만들어준다. 예를 들자면 다음과 같다. - values() : 선언된 순서대로 열거형의 모든 값을 포함하는 배열을 반환하는 메서드 컴파일하면서 생긴다. - T valueOf(Class<T> enumType, String name) : 지정된 열거형에서 name과 일치하는 열거형 상수를 반환한다. 컴파일하면서 생긴다. - Class<E> getDeclaringClass() : 열거형의 객체를 반환한다. - String name() : 열거형 상수의 이름을 문자열로 반환한다. - int ordinal() : 열거형 상수가 정의된 순서를 반환한다.(0부터 시작한다)
ordinal()? 문제가 있다. 이걸 기반해서 코드를 작성하면 위험하다. 왜냐하면 0~… int를 반환하는데 순서가 바뀌면 위험할 수 있다. 대부분의 프로그래머들은 이걸 쓸 일 없다. 내부용이다.
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(); } public static void main(String[] args) { if (args.length != 1) { System.err.println("Usage: java Planet <earth_weight>"); System.exit(-1); } double earthWeight = Double.parseDouble(args[0]); double mass = earthWeight/EARTH.surfaceGravity(); for (Planet p : Planet.values()) System.out.printf("Your weight on %s is %f%n", p, p.surfaceWeight(mass)); } }
Java
복사
Enum 간단한 정의 방법 출처 : oracle tutorial
이렇게 보면 Enum을 왜 만들었나 싶다. 당근 classstatic final로 상수를 정의하면 될 것 같은데 왜 만든 것일까?필자가 찾아보고 개인적인 견해에 의해서 적어본다면 데이터의 추상적인 부분을 가시적으로 만들어주기 위함이라고 생각한다. 예를 들자면 맥북을 판매하는 서비스를 만든다고 가정해보자. 그럼 다음과 같은 코드를 만들 수 있다.
public class Test{ static class MacBook{ public static final int MAC_13= 1_000_000; public static final int MAC_16= 2_000_000; } public static void main(String[] args) { int price =getPrice(MacBook.MAC_13); // 100만원 입니다. 출력 } public static int getPrice(int price){ int type = price; switch (type) { case MacBook.MAC_13: System.out.println("100만원 입니다."); return MacBook.MAC_13; case MacBook.MAC_16: System.out.println("200만원 입니다."); return MacBook.MAC_16; default: throw new NullPointerException("가격정보가 없습니다"); } } }
Java
복사
하지만 이것은 맥북이 인텔 맥북만 존재할때 이야기였다. 그런데 맥북에 m1 맥북이 추가되어 우리는 상수를 고쳐야할 일이 생겨버렸다. 하지만 인텔맥과 m1 맥이 동일한 가격 동일한 성능으로 출시되었기 때문에 이렇게 이름을 바꿔줄 수 밖에 없었다. 그런데 이렇게 하면 동일한 맥북인데 이름을 다르게 할 수 밖에 없었다. 또한 이렇게 하면 개발자의 실수를 부추기는 코드가 되어버린다. 그 예시는 아래 첫번째 코드와 같다. 다음 문제는 아주 심각한 문제를 일으킨다. 다른 메서드에 개념적으로 다른 어떠한 값을 집어넣어도 type이 동일하기 때문에 컴퓨터는 알아차리지 못한다(아까 언급한 추상적인 부분이다). 이 문제를 해결하기 위해서 아래의 두번째 코드와 같이 인터페이스를 사용하면 되지만 그건 안티패턴이다. 그럼 어떻게 해야할까?
static class MacBook{ public static final int MAC_13 = 1_000_000; public static final int MAC_16 = 2_000_000; public static final int M1_MAC_13 = 1_000_000; public static final int M1_MAC_16 = 2_000_000; } ... public class Test{ static class MacBook{ public static final int MAC_13 = 1_000_000; public static final int MAC_16 = 2_000_000; public static final int M1_MAC_13 = 1_000_000; public static final int M1_MAC_16 = 2_000_000; } public static void main(String[] args) { int price = getIntelMacPrice(MacBook.M1_MAC_13); // 인텔맥의 가격을 가져오는 메서드에 M1맥을 집어 넣어도 실행이 되어버린다. // 내가 원하는 것은 인텔맥13형인데?. } public static int getIntelMacPrice(int price){ int type = price; switch (type) { case MacBook.MAC_13: System.out.println("100만원 입니다."); return MacBook.MAC_13; case MacBook.MAC_16: System.out.println("200만원 입니다."); return MacBook.MAC_16; default: throw new NullPointerException("가격정보가 없습니다"); } } }
Java
복사
해결하려는 시도
interface IntelMac{ public static final int MAC_13 = 1_000_000; public static final int MAC_16 = 2_000_000; } interface M1Mac{ public static final int MAC_13 = 1_000_000; public static final int MAC_16 = 2_000_000; }
Java
복사
안티패턴
이런 이유에서 Enum이 탄생하게 되었다. 위의 코드를 Enum Type으로 수정하면 다음과 같이 코드를 만들 수 있다. 조금 다른점은 이제는 Enum M1Mac에 정의된 MAC_13형을 넣으면 컴파일러가 잡아준다.
public class Test{ enum IntelMac{ MAC_13(1_000_000), MAC_16(2_000_000); private int price; IntelMac(int price) { this.price = price; } public int getPrice(){ return price; } } enum M1Mac{ MAC_13(1_000_000), MAC_16(2_000_000); private int price; M1Mac(int price) { this.price = price; } public int getPrice(){ return price; } } public static void main(String[] args) { int price = getIntelMacPrice(IntelMac.MAC_13); } public static int getIntelMacPrice(IntelMac mac){ switch (mac) { case MAC_13: System.out.println("100만원 입니다."); return IntelMac.MAC_13.getPrice(); case MAC_16: System.out.println("200만원 입니다."); return IntelMac.MAC_16.getPrice(); default: throw new NullPointerException("가격정보가 없습니다"); } } }
Java
복사
이렇듯 Enum은 서로 다른 개념의 충돌할때 개발자가 그걸 가시적으로 볼 수 있는 기회를 제공해준다.

Enum 특징

Enum의 특징은 엄연한 Type이라는 점이다(Type이 존재한다는건 특성 - 객체라는 의미와도 같다고 생각한다). 즉, Enum도 클래스다. 이 외에도 몇가지 특징을 나열하면 다음과 같다.
Eunm의 생성자의 접근 제어자는 private이다.
열거형 멤버 중 하나를 호출하면, 열거된 모든 상수의 객체가 생성된다.
열거된 모든 상수는 각각의 인스턴스를 만들며 모두 public static final이다.
java.lang.Enum? 열거형의 조상이다. 모든 열거형은 Enum클래스를 상속받기 때문에 enum type은 별도의 상속을 받을 수 없다. 기본 제공 메서드들도 여기에 포함되어 있다.

EnumSet?

열거형을 위한 Set 인터페이스 구현체이다. HashSet과 비교하면 성능의 이점이 많기 때문에 열거형 데이터를 위한 Set이 필요한 경우 EnumSet을 사용하는 것이 좋다.
특징
EnumSetAbstract class를 상속하고 Set interface를 구현한다.
오직 열거형 상수만을 값으로 가질 수 있다. 모든 값은 enum type이어야 한다.
null은 허용하지 않는다. npe도 허용하지 않는다.
ordinal 값의 순서대로 요소가 저장된다.
Threa-not-safe이다. 동기식으로 사용하려면 Collections.synchronizedSet 을 사용하거나, 외부에서 동기화를 구현해야한다. - 이게 걸려서 사용하기 조금 꺼려지는 부분도 있는 것 같다. Set<MyEnum> s = Collections.synchronizedSet(EnumSet.noneOf(MyEnum.class));
기본 연산 시간 복잡도가 O(1)이다.
EnumSet 내부의 구현체가 RegularEnumSet, JumboEnumSet 으로 나뉜다. (그냥 이런게 있다.)