Search
moon
sun
❓

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을 왜 만들었나 싶다. 당근 class에 static 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을 사용하는 것이 좋다.
특징
•
EnumSet은 Abstract 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 으로 나뉜다. (그냥 이런게 있다.)