https://github.com/whiteship/live-study
백기선님 자바 기초 스터디 11주차
목표
자바의 열거형에 대해 학습하세요.
학습할 것 (필수)
- enum 정의하는 방법
- enum이 제공하는 메소드 (values()와 valueOf())
- java.lang.Enum
- EnumSet
enum 정의하는 방법
열거형은 서로 관련된 상수를 편리하게 선언하기 위한 것으로 여러 상수를 정의할 때 사용하면 유용하다.
특히 자바의 열거형은 '타입에 안전한 열거형(typesafe enum)' 이라서 실제 값이 같아도 타입이 다르면 컴파일 에러가 발생한다.
enum Kind { CLOVER, HEART, DIAMOND, SPADE }
enum VALUE { TWO, THREE, FOUR }
...
if (Kind.CLOVER == VALUE.TWO) // 컴파일 에러. 값은 같지만 타입이 다름
열거형을 정의하는 방법은 간단하다. 다음과 같이 괄호 { } 안에 상수의 이름을 나열하기만 하면 된다.
enum 열거형 이름 { 상수명1, 상수명2, ... }
예를 들어, 동서남북 4방향을 상수로 정의하는 열거형 Direction은 다음과 같다.
enum Direction { EAST, SOUTH, WEST, NORTH }
이 열거형에 정의된 상수를 사용하는 방법은 '열거형이름.상수명'이다. 클래스의 static 변수를 참조하는 것과 동일하다.
class Unit {
int x, y; //유닛의 위치
Direction dir; //열거형을 인스턴스 변수로 선언
void init() {
dir = Direction.EAST; //유닛의 방향을 EAST로 초기화
}
}
열거형 상수간의 비교에는 '=='를 사용할 수 있다. equals()가 아닌 '=='로 비교가 가능하다는 것은 그만큼 빠른 성능을 제공한다는 얘기다.
그러나 '<', '>'와 같은 비교연산자는 사용할 수 없고 compareTo()는 사용가능하다.
if (dir == Direction.EAST) {
x++;
} else if (dir > Direction.WEST) { //에러. 열거형 상수에 비교연산자 사용불가
...
} else if (dir.compareTo(Direction.WEST) > 0) { //compareTo()는 가능
...
}
다음과 같이 switch문의 조건식에도 열거형을 사용할 수 있다.
void move() {
switch (dir) {
case EAST: //Direction.EAST라고 쓰면 안된다.
x++;
break;
case WEST:
x--;
break;
}
}
이 때 주의할 점은 case문에 열거형의 이름은 적지 않고 상수의 이름만 적어야 한다는 제약이 있다. 아마도 그렇게 하는 것이 오타도 줄일 수 있고 보기에도 간결하기 때문인 것 같다.
enum이 제공하는 메소드 (values()와 valueOf())
values()
values()는 열거형의 모든 상수를 배열에 담아 반환한다.
Direction[] dArr = Direction.values();
for (Direction d : dArr) {
System.out.println(d);
}
//Result
EAST
SOUTH
WEST
NORTH
valueOf()
valueOf()메서드는 열거형 상수의 이름으로 문자열 상수에 대한 참조를 얻을 수 있게 해준다.
Direction d = Direction.valueOf("WEST");
System.out.println(d);
System.out.println(Direction.WEST == Direction.valueOf("WEST"));
//Result
WEST
true
java.lang.Enum
Direction[] dArr = Direction.values();
for (Direction d : dArr) {
System.out.printf("%s = %d%n", d.name(), d.ordinal());
}
//Result
EAST = 0
SOUTH = 1
WEST = 2
NORTH = 3
name()은 열거형 상수의 이름, ordinal()은 열거형 상수가 정의된 순서(0부터 시작)를 정수로 반환한다.
앞서 소개한 values(), valueOf()를 포함해 이러한 메서드들은 모든 열거형의 조상인 java.lang.Enum클래스에 정의된 메서드이다.
열거형에 멤버 추가하기
Enum클래스에 정의된 ordinal()이 열거형 상수가 정의된 순서를 반환하지만, 만약 열거형 상수의 값이 불연속적인 경우에는 다음과 같이 열거형 상수의 이름 옆에 원하는 값을 괄호()와 함께 적어주면 된다.
enum Direction { EAST(1), SOUTH(5), WEST(-1), NORTH(10) };
그리고 지정된 값을 저장할 수 있는 인스턴스 변수와 생성자를 새로 추가해 주어야 한다.
enum Direction {
EAST(1), SOUTH(5), WEST(-1), NORTH(10); //끝에 ';'를 추가해야 한다.
private final int value; //정수를 저장할 필드(인스턴스 변수)를 추가
Direction(int value) { //생성자를 추가
this.value = value;
}
public int getValue() {
return value;
}
}
열거형의 인스턴스 변수는 반드시 final이어야 한다는 제약은 없지만, value는 열거형 상수의 값을 저장하기 위한 것이므로 final을 붙였다.
그리고 외부에서 이 값을 얻을 수 있게 getValue()도 추가하였다.
열거형 Direction에 새로운 생성자가 추가되었지만, 열거형의 객체를 생성할 수 없다. 열거형 생성자는 제어자가 묵시적으로 private이기 때문이다.
Direction d = new Direction(1); //에러. 열거형의 생성자는 외부에서 호출불가
필요하다면, 다음과 같이 하나의 열거형 상수에 여러 값을 지정할 수도 있다. 다만 그에 맞게 인스턴스 변수와 생성자 등을 추가해준다.
enum Direction {
EAST(1, ">"), SOUTH(2, "V"), WEST(3, "<"), NORTH(4, "^");
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;
}
}
EnumSet
EnumSet은 enum클래스로 작동하기 위해 특화된 Set 컬렉션이라고 할 수 있다. EnumSet의 내부는 비트 벡터로 구현되었다.
EnumSet은 추상 클래스이며, RegularEnumSet, JumboEnumSet 2가지의 EnumSet구현체를 제공한다.
RegularEnumSet은 비트 벡터를 표현하기 위해 단일 long자료형을 사용한다. long의 각 비트는 enum값을 나타낸다.
long은 64비트이기 때문에 RegularEnumSet은 64개의 원소를 저장할 수 있다.
JumboEnumSet은 long요소의 배열을 비트 벡터로 사용한다. 이를 통해 64개 이상의 원소를 저장한다. RegularEnumSet과 비슷하게 작동하지만, 저장된 배열 인덱스를 찾기 위해 몇 가지 추가 계산을 수행한다.
이 때문에 EnumSet 정적 팩토리 메서드는 enum 원소 수에 따라 구현체를 다르게 선택한다.
if (universe.length <= 64)
return new RegularEnumSet<>(elementType, universe)
else
return new JumboEnumSet<>(elementType, universe)
EnumSet은 다양한 방식으로 만들 수 있다. Direction 열거형이 있다고 하자.
enum Direction { EAST, SOUTH, WEST, NORTH }
먼저 allOf()로 모든 요소를 포함하는 EnumSet을 만들 수 있다.
EnumSet<Direction> set = EnumSet.allOf(Direction.class);
set.forEach(System.out::println);
//Result
EAST
SOUTH
WEST
NORTH
noneOf()를 사용하면 빈 Direction 컬렉션을 갖는 EnumSet을 만들 수 있다.
EnumSet<Direction> set = EnumSet.noneOf(Direction.class);
of()를 사용하면, 들어갈 요소를 직접 입력하여 EnumSet을 생성할 수 있다.
EnumSet<Direction> set = EnumSet.of(Direction.EAST, Direction.WEST);
complementOf()를 사용하면, 원하는 요소를 제거하고 EnumSet을 생성할 수 있다.
EnumSet<Direction> set = EnumSet.complementOf(EnumSet.of(Direction.EAST, Direction.WEST));
set.forEach(System.out::println);
//Result
SOUTH
NORTH
copyOf()를 사용하면, 다른 EnumSet의 모든 요소를 복사하여 EnumSet을 만들 수 있다.
EnumSet.copyOf(EnumSet.of(Direction.EAST, Direction.WEST));
실제 Set컬렉션처럼 EnumSet 역시 add(), contains(), remove()메서드를 사용해 컬렉션을 관리할 수 있다.
EnumSet의 모든 메서드는 산술 비트 연산을 사용하므로 일반적인 연산이 매우 빠르게 계산된다.
더욱이 EnumSet은 비트 벡터의 특성상 더 작은 메모리를 사용한다.
'java > java' 카테고리의 다른 글
[Java] 애노테이션 (1) | 2021.09.05 |
---|---|
[Java] 다중 조건 정렬 (0) | 2021.08.29 |
[Java] 멀티쓰레드 프로그래밍 (1) | 2021.08.23 |
[Java] 예외 처리 (1) | 2021.08.12 |
[Java] 인터페이스 (2) | 2021.07.27 |