Enum 열거형 타입
•
Enum은 열거, 목록, 일람표라는 뜻을 가지고 있는 Enumeration의 약자로, 한글로 열거형이라 부른다. 열겨형은 이름에서 알 수 있듯이, 요소나 멤버로 불리는 명명된 값들의 집합을 이루는 자료형이다. 쉽게 생각해서, 상수 데이터들의 집합으로 치부하면된다.
•
요일은 월,화,수,목,금,토 7가지 밖에 없고, 계절도 봄,여름,가을,겨울 4가지 계절 밖에 없으며, 주사위 눈은 1,2,3,4,5,6 6개 값밖에 없다. 이렇게 한정된 값을 가지는 데이터들의 묶음을 열거형 타입인 Enum으로 묶어주면 보다 구조적으로 프로그래밍 할 수 있다.
과거에 상수를 정의했던 방법들
•
final 상수
◦
final 제어자를 사용해 변수를 상수화하고, static으로 메모리에 한 번만 할당되게 설정하는 방법이 있다.
class EnumExample {
private final static int MONDAY = 1;
private final static int TUESDAY = 2;
private final static int WEDNESDAY = 3;
private final static int THURSDAY = 4;
private final static int FRIDAY = 5;
private final static int SATURDAY = 6;
private final static int SUNDAY = 7;
public static void main(String[] args) {
int day = EnumExample.MONDAY;
switch (day) {
case EnumExample.MONDAY:
System.out.println("월요일 입니다.");
break;
case EnumExample.TUESDAY:
System.out.println("화요일 입니다.");
break;
case EnumExample.WEDNESDAY:
System.out.println("수요일 입니다.");
break;
}
}
}
Java
복사
◦
하지만 이 방법은 접근 제어자들 때문에 가독성이 별로 좋지 못하다.
•
인터페이스 상수
◦
인터페이스 내에도 상수를 선언할 수 있는데, 인터페이스의 멤버는 public static final 속성을 생략할 수 있기 때문에 조금 더 간략하게 작성할 수 있다.
interface DAY {
int MONDAY = 1;
int TUESDAY = 2;
int WEDNESDAY = 3;
int THURSDAY = 4;
int FRIDAY = 5;
int SATURDAY = 6;
int SUNDAY = 7;
}
interface MONTH {
int JANUARY = 1;
int FEBRUARY = 2;
int MARCH = 3;
int APRIL = 4;
int MAY = 5;
int JUNE = 6;
int JULY = 7;
...
}
Java
복사
◦
문제는 여기서의 상수가 결국 정수값을 가진다는 것이다. 상수는 변하지 않는 값을 의미하지만 고유한 값이라는 의미도 어느정도 내포하고 있다. 때문에 위의 DAY와 MONTH를 값을 서로 비교하는 로직이 컴파일 에러 없이 동작이 된다.
◦
이렇게 제약적이지 않은 요소들을 사용하게 되면 프로그램의 크기가 커질수록 버그나 오류의 발생 확률을 높이게 된다.
•
자체 클래스 상수
◦
인터페이스 상수의 단점을 개선하기 위해, 상수를 정수값으로 구성하는게 아니라 자체 클래스를 인스턴스화 해서 사용하는 기법이 등장했다. 자기 자신의 객체를 인스턴스화하고 final static으로 고유 객체를 만들어 상수처럼 활용하는 방법이다.
class Day {
// 자기 자신 객체를 인스턴스화 하고 final static 화 함으로써 고유의 객체 상수를 얻게 됨
public final static Day MONDAY = new Day();
public final static Day TUESDAY = new Day();
public final static Day WEDNESDAY = new Day();
}
class Month {
public final static Month JANUARY = new Month();
public final static Month FEBRUARY = new Month();
public final static Month MARCH = new Month();
}
Java
복사
◦
이 방법의 문제점은 다시 가독성이 안 좋아졌다는 것이고 if문에는 상관없지만, switch 문의 조건식에는 들어가는 데이터 타입이 제한적이기 때문에 사용할 수 없다는 단점이 있다.
•
enum 상수
◦
위의 문제들 대문에 자바에서 아예 상수만 다루는 enum 타입 클래스를 만들어 배포하게 되었다.
enum Day{
MONDAY,TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
}
enum Month{
JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY,
AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER;
}
Java
복사
Enum의 취지와 장점
•
열거형의 핵심은 상수값을 단순히 정수로 치부하지 않고, 객체 지향적으로 객체화하여 관리한다는 것이다.
•
C언어의 enum은 그냥 정수값이고 C++의 enum은 타입이지만, Java의 enum은 인터페이스와 같이 독립된 특수한 클래스로 구분된다.
•
자바의 열거형은 일종의 객체이기 때문에 힙(heap) 메모리에 저장되며, 각 enum 상수들은 별개의 메모리 주소값을 가져 완벽히 독립된 상수를 구성할 수 있다.
•
enum을 사용하게 다음과 같은 장점들이 있다.
◦
코드가 단순해지며 가독성이 좋아진다.
◦
허용 가능한 값들을 제한하여, enum은 본질적으로 Thread safe인 싱글톤 객체로 타입 안전성을 보장할 수 있다.
◦
키워드 enum을 사용하기 때문에 구현의 의도가 분명하게 나타난다.
◦
switch 문에서도 사용 가능하다
◦
자동완성이나 오타검증, 텍스트 리팩토링과 같이 IDE의 지원을 받을 수 있다.
◦
리팩토링 시 enum에서 한번에 관리하기 때문에 변경 범위가 최소화된다.
Enum 기본 문법
•
Enum 선언하기
◦
열거형은 상수 데이터들의 집합이므로, 배열처럼 상수로 사용할 이름들을 나열하여 표현하면 된다.
◦
enum의 이름은 클래스와 마찬가지로 CarmelCase로 작성, 열거 상수는 관례적으로 모두 대문자와 _로 작성한다.
enum Season {
SPRING,
SUMMER,
FALL,
WINTER
}
enum Week {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}
Java
복사
•
Enum 참조 방식
◦
Enum 타입도 하나의 데이터 타입이기 때문에 변수를 선언해 사용하면 된다.
Week monday = Week.MONDAY;
Season spring = Season.SPRING;
Java
복사
◦
enum은 reference 타입으로 JVM에서 힙 영역에 저장되는데, 스택 영역의 변수들이 힙 영역에 있는 데이터의 주소값을 저장하여 참조 형태를 가진다. 이러한 이유로 아래와 같이 enum 타입을 비교하면 둘이 바라보는 주소가 같기 때문에 서로 비교할 수 있다.
Week today = null;
today = Week.SUNDAY;
System.out.println(today == Week.SUNDAY); // true
Java
복사
Enum 메소드 종류
•
모든 Enum 타입은 java.lang.Enum 클래스를 상속하기 때문에 java.lang.Enum에 선언되어 있는 메서드를 이용할 수 있다.
메서드 | 설명 | 리턴 타입 |
name() | 열거 객체의 문자열을 리턴 | String |
ordinal() | 열거 객체의 순번(0부터 시작)을 리턴 | int |
compareTo() | 열거 객체를 비교해서 순번 차이를 리턴 | int |
valueOf(String name) | 문자열을 입력받아서 일치하는 열거 객체를 리턴 | enum |
values() | 모든 열거 객체들을 배열로 리턴 | enum[] |
•
name() 메서드
◦
열거 객체가 가지고 있는 열거 타입을 정의할 때 사용한 상수 이름을 문자열을 리턴한다.
Week w = Week.FRIDAY;
// 열거 객체의 문자열을 리턴
String weekName = w.name();
System.out.println(w.name()); // FRIDAY
Java
복사
•
ordinal() 메서드
◦
열거 타입을 정의할 때 주어진 순번을 리턴한다. 0번째부터 시작하여 전체 열거 객체 중 몇 번째 열거 객체인지 알려준다.
Week w = Week.FRIDAY;
// 열거 객체의 순번(0부터 시작)을 리턴
// 전체 열거 객체 중 몇 번째 열거 객체인지 알려준다
int weekNum = w.ordinal();
System.out.println(weekNum); // 4
Java
복사
•
compareTo() 메서드
◦
매개값으로 주어진 열거 객체와 비교하여 순번의 차이를 리턴한다.
// 열거 객체를 비교해서 순번 차이를 리턴 (시작점을 어느 열거 객체의 기준으로 몇번째 위치하는지)
Week w1 = Week.TUESDAY; // 2
Week w2 = Week.SATURDAY; // 6
// 열거 객체가 매개값의 열거 객체보다 순번이 빠르다 → 음수를 리턴
int compare1 = w1.compareTo(w2); // SATURDAY 기준으로 TUESDAY 위치 (6에서 2가 되기 위한 값)
System.out.println(compare1); // -4
// 열거 객체가 매개값의 열거 객체보다 순번이 늦다 → 양수를 리턴
int compare2 = w2.compareTo(w1); // TUESDAY 기준으로 SATURDAY 위치 (2에서 6가 되기 위한 값)
System.out.println(compare2); // 4
Java
복사
•
valueOf() 메서드
◦
매개값으로 주어지는 문자열과 동일한 문자열을 가지는 열거 객체를 리턴한다.
// 문자열을 입력받아서 일치하는 열거 객체를 리턴
Week w3 = Week.valueOf("SUNDAY");
System.out.println(w3 == Week.SUNDAY); // true
Java
복사
•
values() 메서드
◦
열거 타입의 모든 열거 객체들을 배열로 만들어 리턴한다.
// 모든 열거 객체들을 배열로 리턴
Week[] w4 = Week.values();
System.out.println(Arrays.toString(w4)); // [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
Java
복사
java.lang.Enum 클래스
•
위에서 언급했듯이, 모든 클래스가 Object 클래스를 자동 상속하는 것처럼 모든 Enum 클래스도 java.lang.Enum 클래스를 상속받는다. 마찬가지로 java.lang.Enum 클래스도 Object 클래스를 상속받는데, 그래서 enum에서도 Object 클래스의 메소드를 그대로 사용이 가능하다.
•
하지만 Enum 클래스에서는 Object 클래스의 다음 4가지 메소드는 오버라이딩하지 못하도록 final화 하여 막아두었다. 막아둔 이유는 enum은 고유한 상수값이기 때문에 오버라이딩해 바꿔버리면 고유성이 깨지기 때문이다.
메소드 | 내용 |
clone() | 객체를 복제하기 위한 메소드
하지만, 이 메소드는 enum 클래스에서 사용하면 안된다. 만약 호출될 경우에는 CloneNotSupportedException 이라는 예외를 발생시키도록 되어있다 |
finalize() | GC가 발생할 때 처리하기 위한 메소드 |
hashCode() | int 타입의 해시 코드 값을 리턴하는 메소드 |
equals() | 두 개의 객체가 동일한지를 확인하는 메소드 |
Enum 고급 문법
•
enum 매핑
◦
enum 타입을 다음과 같이 내부의 문자열 필드를 두어 고유한 문자열을 출력할 수 있도록 만들 수 있다.
enum Season {
SPRING("봄"),
SUMMER("여름"),
FALL("가을"),
WINTER("겨울");
// 문자열을 저장할 필드
private String season;
// private 생성자 (싱글톤)
private Season(String season) {
this.season = season;
}
public String getSeason() {
return season;
}
}
Java
복사
public static void main(String[] args) throws Exception {
Season s = Season.SUMMER;
System.out.println(s.name()); // 열거 객체명 출력 : SUMMER
System.out.println(s.getSeason()); // 매핑된 열거 데이터 출력 : 봄
}
Java
복사
◦
이러한 사용이 가능한 이유는 enum 상수 하나마다 자신의 인스턴스를 만들어 public static final 필드로 공개하기 때문이다. 또한 생성자를 private로 두어 final 클래스와 다를 바 없게 만들어, 열거형 인스턴스들이 딱 하나만 존재하는게 보장이 된다.
◦
같은 이유로 아래에 나올 enum에 추상메서드를 확장하는 응용이 가능하다.
◦
위의 예시 코드에 String 필드 대신 List와 같은 컨테이너를 두어 관계를 가시적으로 표현하는 것도 가능하다.
enum CreditCard {
SHINHAN("신한", Arrays.asList("Mr.Life 카드", "Deep Dream 카드", "Deep Oil 카드")),
KB("국민", Arrays.asList("톡톡D 카드", "티타늄 카드", "다담 카드")),
NH("농협", Arrays.asList("올바른 FLEX 카드", "테이크 5 카드", "NH 올원 파이카드"));
private final String Enterprise;
private final List<String> cards;
CreditCard(String name, List<String> cards) {
this.Enterprise = name;
this.cards = cards;
}
String getCard(String cardName) {
return Arrays.stream(CreditCard.values())
.filter(creditCard -> creditCard.equals(cardName))
.findFirst()
.orElseThrow(Exception::new);
}
}
Java
복사
•
enum 확장
◦
위에서 언급했듯이 enum에 추상메서드를 두고 각 상수마다 익명 클래스처럼 메서드를 재정의하여 상수 메서드로도 사용이 가능하다.
enum Operation {
PLUS("+") {
public double apply(double x, double y) {
return x + y;
}
},
MINUS("-") {
public double apply(double x, double y) {
return x - y;
}
},
MULTI("*") {
public double apply(double x, double y) {
return x * y;
}
},
DIVIDE("/") {
public double apply(double x, double y) {
return x / y;
}
};
// 클래스 생성자와 멤버
private final String symbol;
Operation(String symbol) {
this.symbol = symbol;
}
// toString을 재정의하여 열거 객체의 매핑된 문자열을 반환하도록
@Override
public String toString() {
return symbol;
}
// 열거 객체의 메소드에 사용될 추상 메소드 정의
public abstract double apply(double x, double y);
}
Java
복사
public static void main(String[] args) {
double x = 2.5;
double y = 5.0;
// Operation 상수집합의 PLUS 상수를 정의
Operation plus = Operation.PLUS;
// enum 매핑값 출력
String name = plus.toString();
System.out.println(name); // +
// enum 확장 메소드 실행
double result = plus.apply(x, y); // 덧셈을 수행하는 메소드 (Operation.PLUS.apply(x, y) 로 해도됨)
System.out.println(result); // 7.5
// ------------------------------------------------------------------- //
// Operation 상수집합의 PLUS 상수를 정의
Operation multi = Operation.MULTI;
String name = plus.toString();
System.out.println(name); // *
// enum 확장 메소드 실행
double result2 = multi.apply(x, y); // 곱셈을 수행하는 메소드
System.out.println(result2); // 12.5
}
Java
복사
◦
이와 같이 작성하면 상수의 상태와 행위를 한 곳에서 관리하는 것이 가능해지기 때문에, 상수마다 다르게 동작하는 로직이 필요하다면 if문이나 switch문 없이 사용할 수 있다.
Enum과 싱글톤 관계
•
자바에서 enum 타입은 일종의 클래스로, 상수 하나마다 자신의 인스턴스를 만들어 public static final 필드로 공개한 것이다.
•
enum을 클래스처럼 이용할 수는 있지만, 인스턴스화는 할 수 없는데 실제로 new 키워드로 인스턴스를 생성하려하면 에러가 발생한다.
•
enum 타입은 고정된 상수들의 집합으로, 런타임이 아니라 컴파일타임에 모든 값을 알고 있어야한다는 규칙 때문에 이런 제약적 특성을 가지고 있다.
•
이러한 이유로 enum 객체의 생성자를 만들 때는 접근 제어자를 private로 설정해 외부에서 생성하지 못하게 만들어 final 클래스와 다를바 없이 만들어야한다. 이런 특성 때문에, enum 타입은 싱글톤을 구현하는 하나의 방법으로 사용되기도 한다.