본문 바로가기

코틀린

[코틀린] Enum 클래스

안드로이드 개발하는 kancho입니다.

이번 포스팅에서는 코틀린의 Enum 클래스에 대해 알아보고자 합니다.

 

'코틀린 완벽 가이드' 책을 참고하였습니다.

 

 

 

Enum 클래스

미리 정의된 상수들로 이뤄진 제한된 집합을 표현하는 클래스

 

 

기본적인 구조는 아래와 같다.

 

enum class 클래스명 {
   // 상수
   // 멤버 변수, 멤버 함수
}

 

 

enum 클래스를 코드로 알아보자.

 

아래는 요일을 나타내는 WeekDay enum 클래스이다.

WeekDay의 확장 함수인 isWorkDay()는 일하는 요일을 판별하는 함수이다.

 

enum 클래스는 정수, 문자열 등과 비교 시

어떤 값이 가능한 범위 안에 있는지 검사할 필요가 없고

정의한 상수들의 집합을 type-safe 하게 다룰 수 있다.

 

/*
요일을 enum 클래스 안에 상수로 정의
컴파일 시점 상수이므로 보통 대문자를 사용
*/
enum class WeekDay {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

/*
this는 WeekDay를 가리킨다
else 조건에는 SATURDAY, SUNDAY를 제외한 WeekDay의 나머지 상수들이 포함된다
*/
fun WeekDay.isWorkDay() =
    when (this) {
        WeekDay.SATURDAY, WeekDay.SUNDAY -> false
        else -> true
    }

fun main() {
    println(WeekDay.MONDAY.isWorkDay())		// true
    println(WeekDay.SATURDAY.isWorkDay())	// false
}

 

자바에서는 enum class가 아닌 enum 만으로 정의된다.

 

public enum WeekDay {
   MONDAY,
   TUESDAY,
   WEDNESDAY,
   THURSDAY,
   FRIDAY,
   SATURDAY,
   SUNDAY;
}

 

 

Enum 클래스는 미리 정한 전역 상수를 정의한다는 점에서 객체 정의와 비슷하다.

전역 상수로 사용할 수 있는 방법이 없는 위치에서는 enum을 정의할 수 없다. (내부 클래스, 함수 본문)

 

fun main() {
    /* error
    Modifier 'enum' is not applicable to 'local class'
    */
    enum class Direction { NORTH, SOUTH, WEST, EAST }
}

 

 

enum 클래스 활용

 

When

 

when을 사용한 enum

 

enum class Direction {
    NORTH, SOUTH, WEST, EAST
}

// when에서 모든 enum 상수를 다룬 경우 else 생략
fun directionToString(direction: Direction) = when (direction) {
    Direction.NORTH -> "North"
    Direction.SOUTH -> "South"
    Direction.WEST -> "West"
    Direction.EAST -> "East"
}

 

when을 사용했을 때 또 하나의 장점은

기존 enum 상수에서 새로운 상수가 추가되거나 삭제되었을 때

컴파일 단계에서 when 조건들을 검사할 수 있기에 오류가 나는 코드가 없는지 직관적으로 확인할 수 있다.

 

만약 else 가지를 추가하고 

 

fun directionToString(direction: Direction) = when (direction) {
    Direction.NORTH -> "North"
    Direction.SOUTH -> "South"
    Direction.WEST -> "West"
    Direction.EAST -> "East"
    else -> throw IllegalArgumentException("Invalid Direction")
}

 

Direction enum 클래스에 NORTH_EAST라는 새 값을 추가하면

 

enum class Direction {
    NORTH, SOUTH, WEST, EAST, NORTH_EAST
}

 

NORTH_EAST 값은 예외를 발생시킨다.

 

fun main() {
    // IllegalArgument Exception
    println(directionToString(Direction.NORTH_EAST))
}

 

내부적으로 빠진 부분이 없는 when에는 런타임 에러인

NoWhenBranchMatchedException 예외를 던지는 else 가지가 암시적으로 추가된다.

 

// Runtime Exception
public actual open class NoWhenBranchMatchedException : RuntimeException { ... }

 

 

enum 클래스는 import 해서 전체 이름을 적는 것보다 가독성 있게 사용할 수 있다.

 

import Direction.*

enum class Direction {
    NORTH, SOUTH, WEST, EAST
}

fun directionToString(direction: Direction) = when (direction) {
    NORTH -> "North"
    SOUTH -> "South"
    WEST -> "West"
    EAST -> "East"
}

 

 

Enum 클래스 구조의 확장

 

Enum 클래스는

 

  1. 변수나 함수 포함 가능
  2. 확장 함수, 프로퍼티 가능
  3. 객체와 비슷한 구조를 가짐

 

Enum 클래스는 객체와 비슷하게

함수, 프로퍼티, 생성자, init, 내부 클래스 등을 포함할 수 있다.

 

이는 반드시 enum 상수 목록 뒤에 와야 한다.

 

이 때 상수들과 구분하기 위해 상수 목록을 세미콜론(;)으로 끝내야 한다.

 

또한, enum 상수는 enum 클래스의 멤버를 사용할 수 있다.

 

enum class WeekDay {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY,
    FRIDAY, SATURDAY, SUNDAY;	// 상수 마지막에 세미콜론을 붙임

    val toLowerCase = name.lowercase()
    fun isWorkDay() = (this == SATURDAY).not() || (this == SUNDAY).not()
}

// 상수가 멤버 변수와 함수 사용
fun main() {
    println(WeekDay.MONDAY.isWorkDay())		// true
    println(WeekDay.WEDNESDAY.toLowerCase)	// wednesday
}

 

 

Enum 클래스를 사용할 때 만약 생성자가 있으면

상수 뒤에도 생성자 호출이 있어야 한다.

 

enum class WeekDay(val isWorkDay: Boolean) {
    MONDAY(true), TUESDAY(true), WEDNESDAY(true),
    THURSDAY(true), FRIDAY(true),
    SATURDAY(false), SUNDAY(false);

    val isDayOff = isWorkDay.not()
}

fun main() {
    println(WeekDay.MONDAY.isWorkDay)   // true
    println(WeekDay.SUNDAY.isDayOff)    // true
}

 

 

Enum 상수에도 본문이 포함될 수 있다.

하지만 이런 상수에 의해 생겨나는 익명 타입은 코드 밖으로 노출되지 않는다.

 

enum class WeekDay {
    MONDAY { fun startWork() = println("Work week started") }
}

fun main() {
    println(WeekDay.MONDAY.startWork)   // error
}

 

 

MONDAY 상수의 내부적인 자바 코드를 보면 final 한 startWork() 메서드를 볼 수 있다.

 

static final class MONDAY extends WeekDay {
   public final void startWork() {
      String var1 = "Work week started";
      System.out.println(var1);
   }

   MONDAY(String $enum$name, int $enum$ordinal) {
      super((DefaultConstructorMarker)null);
   }
}

 

 

 

Enum 클래스 공통 멤버 사용하기

 

kotlin의 모든 enum 클래스는 kotlin.Enum 클래스의 하위 타입이다.

 

kotlin.Enum 클래스는 name, ordinal 등 enum 클래스가 사용할 수 있는 함수와 프로퍼티를 제공한다.

 

코틀린의 Enum 클래스를 살펴보면

 

public abstract class Enum<E : Enum<E>>(name: String, ordinal: Int): Comparable<E> {

    companion object {}
    
    // Returns the name of this enum constant
    public final val name: String

    // Returns the ordinal of this enumeration constant
    public final val ordinal: Int

    public override final fun compareTo(other: E): Int

    /**
     * Throws an exception since enum constants cannot be cloned.
     * This method prevents enum classes from inheriting from `Cloneable`.
     */
    protected final fun clone(): Any
    public override final fun equals(other: Any?): Boolean
    public override final fun hashCode(): Int
    public override fun toString(): String
}

 

name은 enum 상수의 이름을 반환한다.

Ordinal은 enum 상수값의 순서에 따른 index를 반환한다.

 

enum 상수들은 정의된 순서에 따라 비교할 수 있다. 또한 상수값들의 동등성은 식별자에 의해 정해진다.

 

비교(compare)는 ordinal에 의해 정해진다.

 

Enum 클래스는 동반 객체의 멤버처럼 호출할 수 있는 암시적인 메서드들이 있다.

 

valueOf()는 이름에 해당하는 enum 값을 돌려주거나 예외를 던진다.

 

enum class Direction {
    NORTH, SOUTH, WEST, EAST
}

fun main() {
    println(Direction.EAST.name)    // EAST

    println(Direction.SOUTH.ordinal)    // 1

    println(Direction.WEST == Direction.NORTH)  // false
    println(Direction.EAST < Direction.NORTH)   // false

    println(Direction.valueOf("NORTH")) // NORTH
}

 

 

values()는 정의된 순서대로 모든 enum 값이 들어있는 배열을 돌려준다.

메서드를 호출할 때마다 배열이 새로 생긴다.

 

enum class Direction {
    NORTH, SOUTH, WEST, EAST
}

private val directions = Direction.values()

val Direction.nextDirection get() = directions[ordinal + 1]

fun main() {
    println(Direction.NORTH.nextDirection)  // SOUTH
}

'코틀린' 카테고리의 다른 글

[코틀린] Inline 클래스  (0) 2022.10.23
[코틀린] Data 클래스  (1) 2022.10.23
[코틀린] 람다와 고차함수  (0) 2022.10.17
[코틀린] Scope function (영역 함수)  (0) 2022.10.10
[코틀린] 확장 (Extension)  (0) 2022.10.10