이 글은 백기선님의 라이브 스터디 참여 및 학습 내용에 관한 정리한 글입니다.
열거체(enumeration type) 라고도 부른다. JDK 1.5 이전 자바 버전들에서는 Enum을 사용할 수 없었고, JDK 1.5부터 열거체를 정의한 Enum을 사용할 수 있게 되었다. Enum은 다음과 같은 특징을 가지고 있다.
정리
선업방법
enum [열거체명]{
상수1, 상수2, 상수3...
}
// ---- 예시 ----
enum Color {
RED, BLUE, GREEN, YELLOW, WHITE
}
열거체에 정의된 상수값들은 내부적으로 선언된 순서대로 첫 상수값 0부터 1씩 증가하는 순서로 기본인 값을 가지고 있다. 여기에 추가적인 상수값을 정의하는 것이 가능하며, 개발자가 원하는 커스텀한 상수값을 명시할 수 있으며 이를 정의하기 위해서는 인스턴스 변수 및 생성자를 반드시 추가해주어야한다.
상수 정의 추가
enum Color {
RED(55), GREEN(21), BLUE(10), YELLOW(65);
private final int value;
Color(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
단, Enum 의 생성자는 접근지시자를 public, protected는 사용이 불가하며 default로 private생성자이다. 이유는 Enum 타입은 고정된 상수 집합이기도 하며 런타임이 아닌 컴파일 타임에 모든 값을 알고 있어야한다. 따라서 다른 곳에서 enum 타입에 접근하여 동적으로 어떤 값으로 변경할 수 없도록 막는 것이고 컴파일 시점부터 타입 안정성이 보장되는 것이다.
Type-Safty
Enum을 사용하면 상수들을 좀 더 효율적으로 활용할 수 있고, Type-Safety하게 컴파일 시점에서 비교 체크가 가능해지는 장점이 있다. Enum을 사용했을 때와 사용하지 않았을 때를 비교해보자.
public class App {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String input = sc.nextLine();
if ("INSERT".equals(input)) {
// insert 로직 실행
}
if ("UPDATE".equals(input)) {
// update 로직 실행
}
}
}
이와 같이 class에 필드 변수로 static final로 선언된 RED,BLUE,GREEN을 애플리케이션에서 사용한다고 가정하자, 그러면 사용자에서 넘어온 데이터 값이 String타입이라고 가정하고 INSERT인지 UPDATE인지 등의 비교하여 각 상황에 맞는 로직을 처리하도록 분기 처리를 했다고 생각해보자. 일단 위 코드의 문제점은 개발자가 if문 마다 타이핑으로 비교할 문자열을 정확하게 입력해주어야 하는데 만약에 오타를 냈다면 저 코드는 버그이며 컴파일 시점에서 확인이 불가능하다.
public enum Type {
INSERT, UPDATE
}
public class App {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String input = sc.nextLine();
Type enumType = Type.valueOf(input); // 문자열과 일치한 타입명을 가진 Enum객체
if (Type.INSERT == enumType) {
// insert 로직 실행
}
if (Type.UPDATE == enumType) {
// update 로직 실행
}
// 아래와 같이 switch 구문 사용도 편리해졌다.
swith (enumType) {
case INSERT:
break;
case UPDATE:
break;
default:
break;
}
}
}
Enum 타입을 사용하게 되면 위와 같이 ==
비교 연산이 가능해지고 switch 구문까지 사용이 가능하다. 이렇게 비교하게 된다면 개발자가 컴파일 시점전에 타입 체크를 강제하는 것이 가능해져 문자열 비교시 발생할 수 있는 실수를 줄일 수 있다.
싱글톤
Enum 클래스는 유일하게 하나의 인스턴스를 생성하고 있어 애플리케이션 전역에서 하나의 인스턴스를 바라보게끔 되어 있다. 이러한 점으로 유일한 상수 값들을 애플리케이션으로 효율적으로 관리할 수 있는 장점이 있으나 멀티 쓰레드 환경에서는 유의해야한다. 지난번 챕터에서 멀티 쓰레드 환경과 같은 예시를 들어보자
public class App {
enum Pay {
CARD, COUPON;
private int amount;
private Pay() {
amount = 1000;
}
public void subtractAmount(int amount) {
if (this.amount >= amount) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.amount -= amount;
}
}
public int getAmount() {
return amount;
}
}
public static void main(String[] args) {
PayClient pc = new PayClient(Pay.CARD);
new Thread(pc).start();
new Thread(pc).start();
}
}
class PayClient implements Runnable {
private Pay payType;
public PayClient(Pay type) {
this.payType = type;
}
@Override
public void run() {
while (payType.getAmount() > 0) {
int money = (int) (Math.random() * 3 + 1) * 100;
payType.subtractAmount(money);
System.out.println(Thread.currentThread().getName() + " amount : " + payType.getAmount());
}
}
}
console 출력 :
Thread-1 amount : 600
Thread-0 amount : 600
Thread-0 amount : 400
Thread-1 amount : 400
Thread-1 amount : 100
Thread-0 amount : 100
Thread-0 amount : 100
Thread-0 amount : 100
Thread-1 amount : 100
Thread-0 amount : 100
Thread-0 amount : 100
Thread-1 amount : -100
Thread-0 amount : -100
출력 결과를 보면 알다시피 Enum은 싱글톤이기 때문에 Pay라는 Enum 클래스에 정의한 amount를 멀티 쓰레드 환경에서 참조하고 있고, run메소드에 amount > 0 조건이 있음에도 불구하고 다음과 같은 의도치 못한 결과값이 나오게 되었다. 따라서 멀티 쓰레드 환경에서 위와 같은 방식으로 이용되고 있다면 주의해서 사용해야 한다.
이외에도 Enum 사용 시 장점들이 여러가지 있다
Enum을 통해서 취할 수 있는 장점들이 몇가지 여러 방식들이 있는데, 해당 방식들에 대한 내용은 이해가 되었으나 다음 글에서 좀 더 구체적인 상황과 실무에서 활용한 사례 등 Enum을 통한 여러 장점들에 대해서 구체적인 예시와 장점에 대해서 더욱 명확하게 확인할 수 있다.
https://woowabros.github.io/tools/2017/07/10/java-enum-uses.html
해당 Enum 타입에 정의된 상수들에 대한 배열을 반환
enum Color {
RED, GREEN, WHITE, YELLOW, BLUE, BLACK
}
public class App {
public static void main(String[] args) {
System.out.println(Arrays.toString(Color.values()));
}
}
console 출력 :
[RED, GREEN, WHITE, YELLOW, BLUE, BLACK]
해당 Enum 타입에 상수들 중에서 name과 일치하는 상수를 반환
public class App {
public static void main(String[] args) {
System.out.println(Arrays.toString(Color.values()));
}
}
console 출력 :
GREEN
Enum 상수의 이름을 문자열로 반환
public class App {
public static void main(String[] args) {
System.out.println(Color.BLACK.name());
}
}
console 출력 :
BLACK
상수가 정의된 순서를 반환
public class App {
public static void main(String[] args) {
System.out.println(Color.YELLOW.ordinal());
}
}
console 출력 :
3
이 ordinal 메소드는 사용하지 않는 것을 권장한다. 이 Enum의 순서를 이용해 개발 로직을 짜는 것을 위험요소가 있으며 이 메소드는 Enum 내부에서 사용하기 위해서 만든 것이라 개발자가 이 메소드에 의존된 코드를 작성하는 것은 피해야한다. 이 내용은 JAVA API 문서에서도 말하고 있다
해당 Enum의 객체 정보를 반환
public class App {
public static void main(String[] args) {
System.out.println(Color.RED.getDeclaringClass());
}
}
console 출력 :
class com.practice.practicejava.Color
Enum 클래스는 모든 자바 열거형들의 조상이다. 모든 열거형은 Enum 클래스를 상속받고 있고 Enum Type은 별도로 상속을 받을 수 없다. 또한 대부분의 메소드가 final 이라서 오버라이딩 또한 안되는 메소드들이 많다.
EnumSet은 열거형과 함께 사용하기 위한 Set이라고 볼 수 있다. 열거형 집합은 내부적으로 비트 벡터로 표시 된다고 한다.
특징
관계 구성도
- 이미지 출처 : https://www.geeksforgeeks.org/enumset-class-java
EnumSet의 메소드와 활용 예제
enum Color {
RED, GREEN, WHITE, BLACK, YELLOW, BLUE;
}
public class App {
public static void main(String[] args) {
EnumSet<Color> set1, set2, set3, set4, set5;
set1 = EnumSet.allOf(Color.class);
set2 = EnumSet.of(Color.BLUE, Color.RED, Color.GREEN);
set3 = EnumSet.complementOf(set2);
set4 = EnumSet.range(Color.RED, Color.YELLOW);
set5 = EnumSet.noneOf(Color.class);
// 해당 Enum의 모든 상수를 Set에 등록
System.out.println("set1 : " + set1);
// 같은 Enum 타입의 상수들에서 인자로 주어진 상수들로만 EnumSet 구성
System.out.println("set2 : " + set2);
// 인자로 지정된 Set 집합을 본래 Enum타입 집합에서 반대 요소를 가진 Set 반환
System.out.println("set3 : " + set3);
// 범위로 지정한 요소들의 Set을 반환 (ordinal 순서 기준)
System.out.println("set4 : " + set4);
// noneOf는 해당 Enum 타입에 집합군에서 비어있는 상태의 Set 반환
System.out.println(set5.isEmpty());
set5.add(Color.WHITE);
set5.add(Color.BLACK);
System.out.println("set5 : " + set5);
// Set에 해당 요소를 포함여부를 반환
System.out.println(set5.contains(Color.RED));
}
}
console 출력 :
set1 : [RED, GREEN, WHITE, BLACK, YELLOW, BLUE]
set2 : [RED, GREEN, BLUE]
set3 : [WHITE, BLACK, YELLOW]
set4 : [RED, GREEN, WHITE, BLACK, YELLOW]
true
set5 : [WHITE, BLACK]
false