본문 바로가기
자바

[Java] Enum, 자바의 열거타입을 알아보자

by 책 읽는 개발자_테드 2021. 1. 29.
반응형

 Enum, 자바의 열거타입 

 

이 글은 자바의 열거 타입인 enum에 대해서 설명합니다. enum이 무엇이고, 어떻게 사용하며, 어떠한 구조를 갖는지 살펴봅니다. 

 

학습 목표

 · 열거 타입이란?

 · enum 정의하는 방법

 · enum 사용하기

 · java.lang.Enum

 · enum이 제공하는 메소드 

 · EnumSet


열거 타입이란?

서로 연관된 상수의 집합을 저장하는 자료형

 

데이터 중에는 몇 가지로 한정된 값만을 갖는 경우가 있다. 예들 요일에 대한 데이터(월화수...)는 7개의 값만을 갖고, 계절에 대한 데이터(봄여름...)는 네 개의 값만을 갖는다.

 

이와 같이 한정된 값만을 갖는 데이터 타입이 열거 타입(enumeration type)이다. 

 

enum 정의하는 방법

 

열거 타입(enum)을 정의하기 위해는 열거 타입의 이름을 정하고, 그것으로 소스 파일을 생성해야 한다. 이때 이름은 관례적으로 파스칼케이스를 사용한다.

 

Week.java
MemberGrade.java

 

소스 파일 내용은 다음과 같이 작성한다.

 

public enum 열거타입이름{ ... }

 

소스 파일을 생성했다면, 파일 내부에 열거 상수를 선언해보자. 열거 상수는 열거 타입의 값으로 사용되며, 관례적으로 모두 대문자로 작성한다.

 

public enum Week{

 //열거 상수
   MONDAY,
   TUESDAY,
   WEDNESDAY,
   THURSDAY,
   FRIDAY,
   SATURDAY,
   SUNDAY
}

 

열거 상수가 여러 단어로 구성될 경우에는 단어 사이를 밑줄(_)로 연결하는 것이 관례이다.

 

public enum  LoginResult { LOGIN_SUCCESS, LOGIN_FAILED }

 

enum 사용하기

 

열거 타입도 하나의 데이터 타입이므로 변수를 선언하고 사용한다.

 

열거타입 변수;
Week today;

 

열거 타입 변수를 선언했다면 다음과 같이 열거 상수를 저장할 수 있다.

Week today = Week.SUNDAY;

 

열거 타입 변수도 참조 타입이기 때문에 null 값을 저장할 수 있다.

Week birthday = null;

 

참조 타입 변수를 객체를 참조하는 변수다. 즉, 열거 상수도 객체다. 위에서 정의한 열거 타입 Week의 경우 MONDAY~SUNDAY 7개의 객체로 생성된다.

 

메소드 영역에 생성된 열거 상수가 해당 Week 객체를 각각 참조한다.

 

 

다음 코드에서 today는 스택 영역에 생성된다. today에 저장되는 값은 Week.SUNDAY 열거 상수가 참조하는 객체의 번지이다. 따라서 Week.SUNDAY, today는 같은 Wekk 객체를 참조한다.

 

Week today = Week.SUNDAY;



즉, 아래 연산의 결과는 true가 된다.

today == Week.SUNDAY;

 

이번에는 Calendar 클래스를 이용해 오늘의 요일을 얻고 열거 타입 변수 today에 해당 열거 상수를 대입하는 예제를 살펴보자.

import java.util.Calendar;

 

public class EnumWeekExam {

   public static void main(String [] args){

       Week today = null;
       Calendar cal = Calendar.getInstance();
       int week = cal.get(Calendar.DAY_OF_WEEK); //일(1)~토(7)까지의 숫자를 리턴

       switch (week){
           case 1:
               today = Week.SUNDAY; break;
           case 2:
               today = Week.MONDAY; break;
           case 3:
               today = Week.TUESDAY; break;
           case 4:
               today = Week.WEDNESDAY; break;
           case 5:
               today = Week.THURSDAY; break;
           case 6:
               today = Week.FRIDAY; break;
           case 7:
               today = Week.SATURDAY; break;
       }

       if(today == Week.SATURDAY){
           System.out.printf("토요일에는 강의를 듣습니다.");
       }else{
           System.out.printf("책을 읽습니다.");
       }
   }
}

 

main 메소드 실행 결과

 

enum과 생성자와 메소드

 

enum은 사실 클래스다. 때문에 생성자를 가질 수 있다. 아래와 같이 Fruit 타입을 초기화하여 생성자를 호출해보자.

 

enum Fruit{
    APPLE, PEACH, BANANA;
	
	Fruit(){
		System.out.println("Call Constructor " +this);
	}
}
 
class Main {
    public static void main(String[] args) {
        Fruit type = Fruit.APPLE;
    }
}

 

결과는 다음과 같다.

main() 메소드 결과

생성자가 세 번 호출되었다. 즉, 열거타입에 정의된 열거 상수의 개수 만큼 생성자가 호출된다. 추가적으로 열거타입의 생성자는 private만 허용하기 때문에 생성자 앞에 public을 붙이면 컴파일 오류가 발생한다.

 

enum도 필드의 인스턴스 변수를 가질 수 있다. 때문에 생성자의 매개변수를 통해서 이 값을 초기화할 수도 있다.

 

enum Fruit{
    APPLE("red"), PEACH("pink"), BANANA("yellow");
	
	String color;
	
	Fruit(String color){
		this.color = color;
	}
}

 

enum과 메소드

 

열거형은 메소드를 가질 수도 있다. 메소드를 통해 위에서 정의한 Fruit 열거형의 color 인스턴스 변수를 출력해보자.

 

enum Fruit{
    APPLE("red"), PEACH("pink"), BANANA("yellow");
	
	String color;
	
	Fruit(String color){
		this.color = color;
	}
	
	void getColor(Fruit fruit){
		System.out.println(fruit.name()+":"+fruit.color);
	}
}
 
class Main {
    public static void main(String[] args) 	{
        for(Fruit type: Fruit.values()){
			type.getColor(type);
		}
    }
}

 

결과를 다음과 같다.

main() 메소드 결과

 

java.lang.Enum

 

모든 열거 타입은 컴파일 시에 Enum 클래스를 상속한다. 때문에 다중 상속을 지원하지 않는 java에서 enum 클래스는 상속을 받을 수 없다.

 

java.lang.Enum에는 String name, int ordinal 필드가 존재한다. 여기에는 각각 열거 객체의 상수 이름과 순서가 저장된다. 또한 여러 메소드가 정의되어 있어서 열거 객체에 유용한 기능을 제공한다. 

 

Enum이 제공하는 메소드 

valueOf()

 

매개값으로 주어지는 문자열과 동일한 문자열을 가지는 열거 객체를 리턴한다. 이 메소드는 외부로부터 문자열을 입력받아 열거 객체로 변환할 때 유용하게 사용할 수 있다.

 

Week weekDay = Week.valueOf("SATURDAY");
System.out.println(weekDay);

 

위 코드 결과

 

만약 입력된 문자열에 해당하는 열거 객체가 없다면, 다음과 같은 예외가 발생한다.

 java.lang.IllegalArgumentException: No enum constant

 

values()

열거 타입의 모든 열거 객체들을 배열로 만들어 리턴한다. 다음은 Week 열거 타입의 모든 열거 객체를 배열로 만들어 반복문으로 반복하는 코드다.

Week[] days = Week.values();

for(Week day : days){
   System.out.println(day);
}

 

위 코드 결과

 

이때 배열의 인덱스는 열거 객체의 순번과 같고 각 인덱스 값은 해당 순번의 열거 객체 번지이다. 

 

name() 

열거 객체가 가지고 있는 문자열을 리턴하는 메소드다. 리턴되는 문자열은 열거 타입을 정의할 때 사용한 상수 이름과 동일하다. 아래 name 변수에는 열거 객체 내부의 문자열인 “SUNDAY”가 저장된다.

Week today = Week.SUNDAY;
String name= today.name();

 

ordinal()

전체 열거 객체 중 몇 번째 열거 객체인지 알려주는 메소드다. 열거 객체의 순번은 타입을 정의할 때 주어진 순번을 말하며, 0번부터 시작한다. 예를 들어 열거 타입 Week의 열거 객체 순번은 다음과 같다.

 

public enum Week{
   MONDAY,         // 0번
   TUESDAY,     // 1번
   WEDNESDAY, // 2번
   THURSDAY,     // 3번
   FRIDAY,   // 4번
   SATURDAY,   // 5번
   SUNDAY   // 6번
}

 

아래 코드는 ordinal 변수에 숫자 6을 저장한다.

Week today = Week.SUNDAY;
int ordinal = today.ordinal();

 

compareTo()

 

매개값으로 주어진 열거 객체를 기준으로 전후로 몇 번째 위치하는 지 비교한다. 만약 열거 객체가 매개값의 열거 객체보다 순번이 빠르다면 음수, 늦다면 양수가 리턴된다.

 

아래 예제는 result1에 -2, result2에 2가 저장된다.

 

  Week day1 = Week.MONDAY;
  Week day2 = Week.WEDNESDAY;
  int result1 = day1.compareTo(day2);
  int result2 = day2.compareTo(day1);

 

단, ordinal 메서드를 사용하는 행위는 안티 패턴이 될 가능성이 많다. 사용을 자제하자. (Effective Java)

 

EnumSet

 

EnumSet은 enum 클래스로 작동하기 위해 특화된 Set 컬렉션(데이터를 중복 저장할 수 없고, 순서가 보장되지 않는 자료구조)이라고 할 수 있다. EnumSet은 Set 인터페이스를 구현하고, AbstractSet을 상속한다. 이러한 EnumSet의 내부는 비트 벡터로 구현되었다.

 

https://www.baeldung.com/java-enumset

 

EnumSet을 사용할 때는 몇 가지 고려할 사항이 있다. 

1. 열거형 값만 포함 할 수 있으며, 모든 값은 동일한 열거형이어야 한다.

2. null을 추가할 수 없다.

3. 스레드에 안전하지 않으므로, 필요할 경우 외부에서 동기화한다.

4. 복사본에 fail-safe iterator(장애 발생시 작업을 중단하지 않음) 를 사용하여 컬렉션을 순회할 때, 컬렉션이 수정되어도 ConcurrentModificationException이 발생하지 않는다.

 

EnumSet은 추상 클래스이며, 인스턴스 생성을 위한 다양한 정적 팩토리 메서드가 정의되어있다. JDK에서는 RegularEnumSet, JumboEnumSet 2가지의 EnumSet 구현체를 제공한다.

 

RegularEnumSet은 비트 벡터를를 표현하기 위해 단일 long 자료형을 사용한다. long의 각 비트는  enum 값을 나타낸다. 열거 형의 i번째 값은 i번째 비트에 저장되므로 값이 있는지 여부를 쉽게 알 수 있다. 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 사용하기

 

EnumSet은 다양한 방식으로 만들 수 있다. 다음과 같은 Color 열거형이 있다고 가정하자.

 

public enum Color {
   RED, YELLOW, GREEN, BLUE, BLACK, WHITE
}

 

🔍 먼저 allOf()로 모든 요소를 포함하는 EnumSet을 만들 수 있다. EnumSet을 만들어 내용을 출력해보자.

 

EnumSet<Color> set = EnumSet.allOf(Color.class);
set.forEach(System.out::println);

 

위 코드 결과

 

🔍noneOf()를 사용하면 빈 Color 컬렉션을 갖는 EnumSet을 만들 수 있다.

 

EnumSet<Color> set = EnumSet.noneOf(Color.class);

 

🔍of()를 사용하면, 들어갈 요소를 직접 입력하여 EnumSet을 생성할 수 있다.

EnumSet<Color> set = EnumSet.of(Color.YELLOW, Color.BLUE;

 

🔍complementOf()를사용하면, 원하는 요소를 제거하고 EnumSet을 생성할 수 있다.

EnumSet<Color> set = EnumSet.complementOf(EnumSet.of(Color.BLACK,Color.BLUE));
set.forEach(System.out::println);

 

위 코드 결과

 

🔍copyOf()를 사용하면 다른 EnumSet의 모든 요소를 복사하여 EnumSet을 만들 수 있다.

 

EnumSet.copyOf(EnumSet.of(Color.BLACK, Color.WHITE));

 

EnumSet를 다루는 다른 메소드들을 살펴보면 add(), contains(), remove() 등이 있다.

 

🔍add()를 사용하면 EnumSet에 요소를 추가할 수 있다.

EnumSet<Color> set = EnumSet.of(Color.YELLOW, Color.BLUE;
set.add(Color.RED);

 

🔍 contains()를 사용하면 특정 요소가 EnumSet에 포함되어 있는지 확인할 수 있다.

EnumSet<Color> set = EnumSet.of(Color.YELLOW, Color.BLUE);
set.add(Color.RED);
boolean isContain = set.contains(Color.RED);

if(isContain)
   System.out.println("빨간색 포함");

 

위 코드 결과

 

🔍 remove()를 사용하면 EnumSet에서 특정 요소를 제거할 수 있다.

set.remove(Color.RED);

 

EnumSet 사용의 장점

EnumSet의 모든 메서드(contains 등)는 산술 비트 연산을 사용하여 구현되므로 일반적인 연산이 매우 빠르게 계산된다. 

 

EnumSet은 HashSet 같은 다른 Set 구현체와 비교했을 때, 데이터가 예상 가능한 순서로 저장되어 있고, 각 계산을 하는데 하나의 비트만이 필요하므로 더 빠르다고 할 수 있다. 또한 HashSet 처럼 데이터를 저장할 버킷을 찾는 데 hashcode를 계산할 필요가 없다. 

 

더욱이 EnumSet은 비트 벡터의 특성상 더 작은 메모리를 사용한다.



출처

생활코딩

이것이 자바다

https://www.baeldung.com/java-enumset

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Enum.html

 

 

반응형

댓글