자바의 열거형에 대해 학습한다.
열겨형은 서로 관련된 상수를 편리하게 선언하기 위한 것이다.
열거형을 사용하기 전
class Card {
// kind
static final int CLOVER = 0;
static final int HEART = 1;
static final int DIAMOND = 2;
static final int SPADE = 3;
// value
static final int TWO = 0;
static final int THREE = 1;
static final int FOUR = 2;
final int kind;
final int value;
}
열거형을 사용한 후
class Card {
enum Kind {CLOVER, HEART, DIAMOND, SPADE} // 열거형 Kind를 정의
enum Value {TWO, THREE, FOUR} // 열거형 Value를 정의
final Kind kind;
final Value value;
}
if(Card.CLOVER == Card.TWO) // true
enum 열거형이름 {상수명1, 상수명2, ...}
enum Direction {
EAST(1, ">"), SOUTH(5, "V"), WEST(-1, "<"), NORTH(10, "^");
// 열거형 상수 값을 저장할 인스턴스 변수
private final int value;
private final String symbol;
// 열거형 상수 값을 저장할 생성자
Direction(int value, String symbol) {
this.value = value;
this.symbol = symbol;
}
public int getValue() { return value; }
public String getSymbol() { return symbol; }
}
final
을 붙이는 것이 좋다.private
이므로, 열거형의 객체를 생성할 수 없다.열거형이름.상수명;
열거형상수명.메서드명();
열거형 상수 하나하나가 해당 열거형의 객체이다.
enum Direction {EAST, SOUTH, WEST, NORTH};
위는 다음과 같다.
class Direction {
static final Direction EAST = new Direction("EAST");
static final Direction SOUTH = new Direction("SOUTH");
static final Direction WEST = new Direction("WEST");
static final Direction NORTH = new Direction("NORTH");
private String name;
private Direction(String name) {
this.name = name;
}
}
열거형에 추상메서드를 선언하면 각 열거형 상수가 이 추상 메서드를 반드시 구현해야 한다.
protected
로 선언해야 접근이 가능하다.enum Transportation {
BUS(100) { int fare(int distance) { return distance*BASIC_FARE;}},
TRAIN(150) { int fare(int distance) { return distance*BASIC_FARE;}},
SHIP(100) { int fare(int distance) { return distance*BASIC_FARE;}},
AIRPLANE(300) { int fare(int distance) { return distance*BASIC_FARE;}};
abstract int fare(int distance); // 거리에 따른 요금을 계산하는 추상 메서드
protected final int BASIC_FARE;
Transportation(int basicFare) {
BASIC_FARE = basicFare;
}
public int getBasicFare() { return BASIC_FARE; }
}
Transportation
의 내부 구현 방식은 다음과 같다.
abstract static class Transportation {
static final Transportation BUS = new Transportation(100) {
int fare(int distance) {
return distance * basicFare;
}
};
static final Transportation TRAIN = new Transportation(150) {
int fare(int distance) {
return distance * basicFare;
}
};
static final Transportation SHIP = new Transportation(100) {
int fare(int distance) {
return distance * basicFare;
}
};
static final Transportation AIRPLANE = new Transportation(300) {
int fare(int distance) {
return distance * basicFare;
}
};
protected final int basicFare;
private Transportation(int basicFare) {
this.basicFare = basicFare;
}
public int getBasicFare() {
return basicFare;
}
abstract int fare(int distance); // 거리에 따른 요금을 계산하는 추상 메서드
}
동등비교 : equals()
가 아닌 ==
로 비교가 가능하다.
대소비교는 <
, >
를 사용할 수 없고, compareTo()
를 사용할 수 있다.
switch
문의 조건식에도 열거형을 사용할 수 있다.
enum Direction{EAST, WEST, SOUTH, NORTH}
void move(Direction direction) {
switch(direction) {
case EAST: // Direction.EAST로 작성할 수 없다.
break;
case WEST:
break;
case SOUTH:
break;
case NORTH:
break;
}
}
모든 열거형은 추상 클래스 Enum의 자손이다.
abstract class Enum<E extends Enum<E>> implements Comparable<E> {
static int id = 0; // 열거형 상수의 선언 순서
private int ordinal;
String name = "";
public int ordinal() { return ordinal; }
Enum(String name) {
this.name = name;
ordinal = id++;
}
public int compareTo(E e) {
return ordinal - t.ordinal();
}
}
ordinal
에 저장한다.compareTo
의 인자로 들어오는 클래스가 ordinal()
를 가지고 있어야 하므로 즉, 해당 클래스가 Enum을 상속받아야 하므로 Enum<E extends Enum<E>>
와 같이 선언하였다.모든 열거형이 해당 메소드들을 가지고 있으며, 컴파일러가 자동으로 추가해 준다.
열거형 상수의 이름을 문자열로 반환한다.
열거형 상수가 정의된 순서(0부터 시작)를 정수로 반환한다.
열거형의 모든 상수를 배열에 담아 반환한다.
Direction[] directionArr = Direction.values();
for(Direction d : directionArr) {
System.out.printf("%s = %d%n", d.name(), d.ordinal());
}
열거형의 Class 객체를 반환한다.
지정된 열거형에서 name과 일치하는 열거형 상수를 반환한다.
System.out.println(Direction.WEST == Direction.valueOf("WEST")); // true
Enum 타입을 저장하기 위한 Set 구현체
null
을 추가할 수 없다.null
을 삽입하면, NullPointException
이 발생한다.// Direction의 모든 요소(열거형 상수들)를 포함하는 EnumSet
EnumSet<Direction> set = EnumSet.allOf(Direction.class);
// 아무 요소도 포함하지 않은 EnumSet
EnumSet<Direction> set = EnumSet.noneOf(Direction.class);
// 들어갈 요소를 선택할 수 있는 EnumSet
EnumSet<Direction> set = EnumSet.of(Direction.WEST, Direction.SOUTH);
// 모든 요소에서 원하는 요소를 제거한 EnumSet
EnumSet<Direction> set = EnumSet.complementOf(EnumSet.of(Direction.WEST, Direction.SOUTH));
new
연산자로 생성할 수 없고, 정적 팩토리 메소드로만 생성할 수 있다.RegularEnumSet
, JumboEnumSet
2가지의 EnumSet
구현체를 제공한다.💡
new
가 아닌 정적 팩토리 메소드 방식을 차용한 이유
- 첫째, 개발자는 EnumSet의 구현 객체중 어떤 객체가 가장 적합한지 고를 필요가 없다.
- 둘째, 간단한 EnumSet 초기화 과정
- 모든 열거형 상수들을 EnumSet에 담는 일이 빈번한데, 이 과정을 일일이 개발자가 수행하면 번거롭다.
- 셋째, EnumSet의 확장성
RegularEnumSet
,JumboEnumSet
외의 다른 EnumSet 구현 객체가 추가/삭제되어도 기존에 EnumSet을 사용하던 코드는 변경점이 없다.
추가 - add()
EnumSet<Direction> set = EnumSet.of(Direction.WEST, Direction.SOUTH);
set.add(Direction.NORTH);
삭제 - remove()
EnumSet<Direction> set = EnumSet.of(Direction.WEST, Direction.SOUTH);
set.remove(Direction.WEST);
조회 - contains()
EnumSet<Direction> set = EnumSet.of(Direction.WEST, Direction.SOUTH);
if(set.contains(Direction.WEST)) {
System.out.println("WEST를 포함하고 있습니다.");
}
EnumSet.copyOf(EnumSet.of(Direction.WEST, Direction.SOUTH));
🤔 Set 구현체들이 많이 있는데, 왜 EnumSet을 만든걸까?
- EnumSet은 내부적으로 비트 벡터를 사용하여 메모리 사용을 크게 줄일 수 있다.
- EnumSet의 모든 메서드들은 비트 연산을 사용하여 구현되므로 일반적인 연산이 매우 빠르게 계산된다.
Reference
- 자바의 정석 3rd Edition, 남궁성 지음
- 11주차. Enum
- [Java] Enum, 자바의 열거타입을 알아보자