Live Study 11주차 - Enum


enum 이란

Enum이란, 서로 관련된 상수를 편리하게 선언하기 위한 기술로써, 상수 간 비교에서 리터럴만 비교한다면 Enum 간 비교에서는 타입까지 관리하기 때문에 Type Safety한 효과를 누릴 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Card {
enum Kind {
CLOVER, HEART, DIAMOND;
}

enum Num {
ZERO, ONE, TWO;
}

static final int CLOVER = 0;
static final int HEART = 1;
static final int DIAMOND = 2;

static final int ZERO = 0;
static final int ONE = 1;
static final int TWO = 2;

final Kind kind;
final Num num;

public Card(Kind kind, Num num) {
this.kind = kind;
this.num = num;
}

public static void main(String[] args) {
System.out.println( Card.CLOVER == Card.ZERO );
// System.out.println( Card.Kind.CLOVER == Card.Num.ZERO ); 컴파일 에러
}
}
1
true

출처 : 남궁성 - Java의 정석

카드의 모양(Kind)과 숫자(Num)에 따라 상수를 Enum으로 분류했다. main()에서 비교를 해보면, 첫번째 비교문의 결과는 콘솔에 true로 출력된다.

그러나 두번째 비교문은 컴파일 에러가 발생했다.

연산자 문제인가 싶어서 .equals() 메서드로 다시 비교를 시도해보았다.

이 코드는 항상 false를 반환한다고 한다. 같은 타입이 아니기 때문이다.

두번째 비교문에서 비교 대상은 Card.Kind타입의 CloverCard.Num 타입의 Zero를 비교하고 있기 때문이다.

그래서 Enum을 사용할 경우 타입 안정성(Type Safety)를 보장받을 수 있다고 한다. 이것이 Enum을 사용하는 목적이다.


enum 정의하는 방법

1
enum [열거형 이름] { 상수명1, 상수명2, ... }

enum은 위와 같은 포맷으로 작성한다. 인터페이스 작성과 유사한 면이 있다는 생각이 들었다.

아래는 enum을 사용한 예제코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Unit {
enum Direction {
EAST, SOUTH, WEST, NORTH
}

public static void main(String[] args) {
Direction d1 = Direction.EAST;
Direction d2 = Direction.valueOf("WEST");
Direction d3 = Enum.valueOf(Direction.class, "EAST");

System.out.println("d1 = "+d1);
System.out.println("d2 = "+d2);
System.out.println("d3 = "+d3);

System.out.println("d1==d2 ? => "+(d1==d2));
System.out.println("d2==WEST ? => "+(d2==Direction.WEST));
System.out.println("d1==d3 ? => "+(d1==d3));
System.out.println("d1.equals(d3) ? => "+d1.equals(d3));
// System.out.println("d2 > d3 ? "+(d1 > d3)); error
System.out.println("d1.compareTo(d3) ? => "+(d1.compareTo(d3)));
System.out.println("d1.compareTo(d2) ? => "+(d1.compareTo(d2)));
}
}
1
2
3
4
5
6
7
8
9
d1 = EAST
d2 = WEST
d3 = EAST
d1==d2 ? => false
d2==WEST => true
d1==d3 ? => true
d1.equals(d3) ? => true
d1.compareTo(d3) ? => 0
d1.compareTo(d2) ? => -2

열거형 상수 인스턴스를 각각 선언하여 비교하는 코드이다.

ordinal

이 ordinal에 대해서 주석을 통해 유추하자면 객체의 순서같았다. 영어 단어 뜻으로도 순서를 의미했다.

인텔리제이에서 다시 ordinal을 클릭해서 보니 ordinal은 열거형 상수의 정의된 순서이며, 초기값은 0이라고 한다.

Enum 클래스의 내장 메서드로 .ordinal()을 이용하면 각 Enum 객체의 ordinal을 반환할 수 있다길래 사용해서 출력해보았다.

d1, d2, d3의 각각의 ordinal이 0, 2, 0이라고 한다.

그럼 d1.compareTo(d3)d1의 ordinal에서 d3의 ordinal을 빼야하므로 0,

d1.compareTo(d2)d1의 ordinal에서 d2의 ordinal을 빼므로 -2이가 된다.


enum이 제공하는 메서드 values()와 valueOf()

values()

Enum 객체를 배열에 담아 반환해주는 메서드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class enumTest {
enum Direction {
EAST, SOUTH, WEST, NORTH
}

public static void main(String[] args) {
Direction d1 = Direction.EAST;
Direction d2 = Direction.valueOf("WEST");
Direction d3 = Enum.valueOf(Direction.class, "EAST");

Direction[] dArr = Direction.values();

System.out.println("dArr : "+Arrays.toString(dArr));
}
}
1
[EAST, SOUTH, WEST, NORTH]

valueOf()

파라미터에 입력된 열거형 상수의 이름을 통해 문자열 상수에 대한 참조를 반환하는 메서드이다.

열거형 상수의 이름으로 문자열 상수에 대한 참조를 반환하는 메서드이다.

1
2
Direction d1 = Direction.EAST;
System.out.println(d1.equals(Direction.valueOf("EAST")));
1
true

만약 파라미터에 들어온 문자열 값이 열거형 상수의 이름이 아닐경우, 예외(IllegalArgumentException)을 발생시킨다.


java.lang.Enum

1
2
3
enum Direction {
EAST, WEST, SOUTH, NORTH
}

위의 열거형 상수들의 타입은 Direction이다. 그리고 이 DirectionEnum 클래스의 하위 객체이다.

이처럼 모든 Enum 타입은 Enum 클래스를 상속받는데, 그렇기 때문에 Enum 타입은 다른 클래스를 상속받을 수 없다. 다중상속이 되기 때문이다.

그래서 Enum 클래스에서 정의된 compareTo(), values(), valueOf()와 같은 메서드를 사용할 수 있다.

Enum 클래스에 정의된 생성자가 있지만, 다른 클래스와 달리 접근제한자가 protected로 선언되어있어서 Enum 클래스의 생성자는 개발자가 호출할 수 없다.


EnumSet

Enum을 위한 자료구조형 Set 인터페이스 구현체이다. EnumSet의 상속구조는 다음과 같다.

https://www.geeksforgeeks.org/enumset-class-java/

EnumSet의 주요특징은 아래와 같다.

  • AbstractSet 클래스를 상속하며, Set 인터페이스를 구현한다.
  • 오직 열거형 상수만을 값으로 가질수 있으며, 모든 값은 같은 Enum Type이어야 한다. (제네릭으로 강제하는것이 좋음)
  • Null 값을 가지지 않는다.
  • ordinal(열거형 상수가 정의된 순서)대로 요소가 저장된다.
  • thread-safe하지 않으므로 Collections.synchronizedMap을 사용하거나 외부에서 동기화를 구현해야한다.

EnumSet 주요 메서드

EnumSet.allOf()

Enum 클래스의 열거형 상수를 EnumSet 집합으로 모두 가져오는 메서드이다.

1
2
3
4
5
6
7
8
9
10
public class LiveStudy {
enum Direction { EAST, SOUTH, WEST, NORTH }
public static void main(String[] args){

EnumSet<Direction> set1 = EnumSet.allOf(Direction.class);

System.out.println(set1);
}
}

1
[EAST, SOUTH, WEST, NORTH]

EnumSet.of()

파라미터로 들어온 열거형 상수를 EnumSet 집합으로 반환하는 메서드이다.

파라미터 순대로 EnumSet에 저장되지 않고, ordinal대로 저장된다.

1
2
3
4
5
6
7
8
9
public class LiveStudy {
enum Direction { EAST, SOUTH, WEST, NORTH }
public static void main(String[] args){

EnumSet<Direction> set2 = EnumSet.Of(Direction.NORTH, Direction.SOUTH, Direction.EAST);

System.out.println(set2);
}
}
1
[EAST, SOUTH, NORTH]

complementOf()

Enum 타입의 전체 열거형 상수중 파라미터로 들어온 EnumSet 인스턴스에 없는 열거형 상수를 EnumSet 으로 반환한다.

1
2
3
4
5
6
7
8
9
public class LiveStudy {
enum Direction { EAST, SOUTH, WEST, NORTH }
public static void main(String[] args){

EnumSet<Direction> set3 = EnumSet.complementOf(set2);

System.out.println(set3);
}
}
1
[EAST, WEST]

range()

2개의 파라미터로 들어온 열거형 상수 사이의 열거형 상수들을 EnumSet 집합으로 반환한다. 범위의 인덱스는 ordinal을 따른다.

1
2
3
4
5
6
7
8
9
public class LiveStudy {
enum Direction { EAST, SOUTH, WEST, NORTH }
public static void main(String[] args){

EnumSet<Direction> set4 = EnumSet.range(Direction.SOUTH, Direction.NORTH);

System.out.println(set4);
}
}
1
[SOUTH, WEST, NORTH]

noneOf()

파라미터로 들어온 열거형 클래스의 빈값을 EnumSet 타입으로 반환한다.

1
2
3
4
5
6
7
8
9
public class LiveStudy {
enum Direction { EAST, SOUTH, WEST, NORTH }
public static void main(String[] args){

EnumSet<Direction> set5 = EnumSet.noneOf(Direction.class);

System.out.println(set5);
}
}
1
[]

copyOf()

EnumSet 객체를 복사하는 메서드이다.

1
2
3
4
5
6
7
8
9
10
11
public class LiveStudy {
enum Direction { EAST, SOUTH, WEST, NORTH }
public static void main(String[] args){

EnumSet<Direction> set6, set7;
set6 = EnumSet.of(Direction.WEST, Direction.EAST, Direction.NORTH);
set7 = EnumSet.copyOf(set6);

System.out.println(set7);
}
}
1
[EAST, WEST, NORTH]

add(), remove()

열거형 상수를 더하고 빼는 메서드이다.

1
2
3
4
5
6
7
8
9
10
11
12
public class LiveStudy {
enum Direction { EAST, SOUTH, WEST, NORTH }
public static void main(String[] args){

EnumSet<Direction> set8;
set8 = EnumSet.of(Direction.WEST, Direction.EAST, Direction.NORTH);
set8.remove(Direction.NORTH);
set8.add(Direction.SOUTH);

System.out.println(set8);
}
}
1
[EAST, SOUTH, WEST]

함께 보면 좋을 글