[Kotlin] Enum class

0

개념

목록 보기
2/2
post-thumbnail

안녕하세요, 오늘은 Kotlin에서의 enum class에 대해 다뤄보려고 합니다.
저는 개인적으로 프로젝트를 진행하며 이 enum class를 다양한 상황에서 요긴하게 써먹었던 기억이 있습니다!

Enum class란?

🔗 공식 문서

Enum은 Enumrated type의 준말로, 번역하면 열거형입니다.

Enum은 클래스 내에 상수들을 열거해 관리하는 문법으로, 각 상수들은 각각 상태를 나타내는 '객체'로, property와 function을 가질 수 있는 특징이 있습니다. 
열거형을 정의하려면 enum 키워드를 정의하고, 열거형 멤버의 이름을 지정할 수 있습니다.
Enum 클래스는 타입에 안전한 열거형 클래스로, 관련된 상수들을 그룹화할 때 주로 사용됩니다.

Enum 클래스는 크게 아래의 두 역할로 활용될 수 있습니다.

💡 Enum의 역할
1. 타입을 분류하는 Flag 역할
2. 상수를 저장하는 역할

공식 문서에 따르면 열거형 클래스 사용에는 아래와 같은 예시가 있네요.

1) 기본적 사용 사례 - type-safe 열거형을 구현하는 것

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

NORTH, SOUTH 등을 열거 상수(enum constant)라고 부르며, 앞서 말했듯이 각 열거 상수는 객체입니다. 열거 상수는 쉼표로 구분됩니다.
기본적으로 Enum 클래스의 객체들은 대문자로 작성합니다.
이 경우, Enum의 역할 중 1번으로 사용된 경우일 겁니다.
우리가 일반적으로 생각하는 방향으로는 동서남북 방향이 있을텐데, 이를 하나의 flag로 보고 Direction이라는 열거형 클래스로 묶어준 거죠.

2) 프로퍼티를 사용해 초기화

각 열거형은 열거형 클래스의 인스턴스이므로 다음과 같이 초기화할 수 있습니다.

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}

여기서 enum class Color(val rgb: Int) 코드가 보이실 텐데요,
Enum 클래스 선언 시에 rgb를 생성자로 명시한 걸 볼 수 있습니다. 이 rgb를 열거 상수의 프로퍼티라고 부릅니다.

그리고 앞선 예제와는 다르게 열거 상수를

RED, GREEN, BLUE

식으로 정의하지 않고

RED(0xFF0000), GREEN(0x00FF00), BLUE(0x0000FF)

로 정의한 것은 각 상수 생성 시 그에 대한 프로퍼티 값(여기서는 Int 형식의 rgb)을 지정한 것입니다.
RED(rgb = 0xFF0000) 식으로 프로퍼티 이름을 명시적으로 작성해줄 수도 있습니다.
Color 클래스에 접근할 때 Color.RED.rgb를 쓰면 RED의 rgb 색상을 가져올 수 있습니다.
아까의 Enum 역할 중 2번, 상수를 저장하는 역할을 수행함을 확인할 수 있죠.

기본 개념과 예제에 대해 알아봤으니 이번에는 Enum의 멤버 변수와 함수에 대해 알아보겠습니다.

Enum class 멤버

enum class는 추상 클래스이기 때문에, 각 enum 별로 멤버 변수/함수가 존재함

내부 구조를 한 번 살펴볼까요?

  • 변수
    - name : 호출하는 Enum value의 이름을 반환 (String)
    - ordinal : 호출하는 Enum value의 인덱스를 반환 (Int)

  • 함수: 자체적인 함수는 없고, 상속받은 함수들만 존재
    - Cloneable -> clone() : enum 상수는 복제할 수 없음 → 예외 발생
    - Comparable -> compareTo() : 두 Enum value 간 인덱스의 차를 반환
    - Any -> equals(), hashCode(), toString()

이제 예시를 통해 각 프로퍼티와 함수를 확인해봅시다!

제가 이전에 작성했던 [Android] strings.xml을 활용해 다국어 지원하기 글에서의, 아래 티빙 홈 화면에서의 탭 레이아웃 장르들을 Enum 클래스로도 저장해줄 수 있을 겁니다.

Enum class 정의 (예시)

enum class TabGenreType(val title: String) {
	DRAMA("드라마"),
	VARIETY("예능"),
	MOVIE("영화"),
	SPORTS("스포츠"),
	ANIMATION("애니메이션"),
	NEWS("뉴스");
}

기본 멤버 변수 (Property)

name & ordinal

  • ordinal은 호출하는 Enum value의 인덱스를 반환하기에 DRAMA의 ordinal은 0이 나옵니다.
  • name은 호출하는 Enum value의 이름을 반환하기에 "DRAMA"가 출력됩니다.
  • 미리 정의한 상수의 프로퍼티인 titleDRAMA.title로 출력하면 DRAMA의 title인 "드라마"가 출력됩니다.

Function

clone, compareTo, equals, hashCode, toString

  • clone은 protented 되어있기 때문에 접근할 수 없다고 나옵니다.
  • compareTo를 사용하면 두 상수 간의 인덱스 차를 얻을 수 있습니다.
  • equals로 두 상수가 같은지를 비교해줄 수 있습니다.
  • hashCode로 객체의 해시코드를 구할 수 있습니다.
  • toString으로 객체 이름을 얻을 수 있습니다.

열거 상수를 나열하고, 이름으로 열거 상수를 찾는 방법

Enum 상수를 배열로 만들어 반환하는 메소드

entries & enumValues<Enum>()

둘 다 모든 Enum 상수를 배열 또는 컬렉션으로 반환한다는 점에서 기능은 유사합니다. (TabGenreType 안에 정의된 모든 값들을 가져옴)
또한, 선언 순서대로 컬렉션을 반환한다는 공통된 특징이 있습니다.

실제로 코드를 작성해 보면 위와 같은 출력 결과가 나옵니다.
entries는 자체를 출력하면 Enum class의 상수가 컬렉션 형태로 잘 출력되지만,

차이점은 아래와 같습니다.

항목entriesenumValues<T>()
타입 ⭐️EnumEntries<T> (불변 리스트)Array<T> (가변 배열)
선언 방식컴파일러가 생성한 entries 프로퍼티함수 (리플렉션 사용)
도입Kotlin 1.9Kotlin 1.1
사용 예TabGenreType.entriesenumValues<TabGenreType>()

사용하는 코틀린 버전이 1.9 이상이라면, 가능하면 entries를 사용하는 게 성능이나 타입 안정성, 불변성 측면에서 더 좋습니다.

Kotlin 1.9 이전에는 entries이 없이 values를 사용했습니다.

하지만 values는 아래와 같은 문제점이 있기에, entries로 바꿔 사용하는 것이 권장됩니다. Kotlin 1.9 이상에서 values를 사용한다! 그러면 위와 같이 주의 문구가 표시됩니다.

<values의 문제점>

  • values()를 호출할 때마다 배열을 할당, 복제하기 때문에 성능 이슈의 원인이 됨
  • 기본적으로 변경 가능하고(mutable) 사용자가 수동으로 배열을 리스트로 바꾸게 하는 컬렉션보다 덜 유연한 Array를 리턴함
  • Array를 리턴하기 때문에 enum에 대한 확장 함수 작성이 어려움

🔗 values() 사용 관련 문서

name을 기반으로 해당하는 Enum 상수를 찾아 반환하는 메소드

valueOf() & enumValueOf<Enum>(name : String)

둘 다 일치하는 열거 상수를 찾는 역할을 하며, 결과는 동일합니다.
enumValueOf() 함수는 제네릭 방식으로 Enum class의 상수에 접근할 수 있다.

name에 해당하는 Enum 상수를 찾지 못한다면 IllegalArgumentException이 발생합니다.

Enum 클래스 활용

when

Enum 클래스와 When 표현식을 함께 활용하면 가독성과 타입 안전성 보장 측면에서 많은 이점을 얻을 수 있습니다.

fun getMnemonic(color: Color) =
	when (color) {
		Color.RED -> "Richard"
		Color.GREEN -> "Gave"
		Color.BLUE -> "Battle"
	}
	
println(getMnemonic(Color.BLUE) // Battle

Enum으로 여관된 상수들이 그룹화되기 때문에 When으로 분기 처리 시에 코드를 읽고 이해하기 쉽게 만들 수 있습니다.
또한, When 식에서 단 한 개의 Enum이라도 빠져 있다면 컴파일러가 타입을 검사해 오류를 발생시키기 때문에 타입 안전성을 보장하기 수월합니다.

메소드 작성

enum class에서 커스텀 메소드를 작성할 수 있습니다.
저는 주로 companion object로 필요한 메소드를 정의하곤 하는데요, 아래는 그 예시입니다.

enum class TabGenreType(val title: String) {
    DRAMA("드라마"),
    VARIETY("예능"),
    MOVIE("영화"),
    SPORTS("스포츠"),
    ANIMATION("애니메이션"),
    NEWS("뉴스");
    
    companion object {
	    // id를 통해 TabGenreType 얻기
	    fun getGenreById(index: Int) = entries.find { // 반환 타입: TabGenreType
		    it.ordinal == index
			} ?: DRAMA
			
  		// 각 상수의 title을 목록으로 받아오기
		fun getGenreTitles() = entries.map { // 반환 타입: List<String>
			it.title
		}
    }
}

** 참고) Enum 클래스 안에 메소드를 사용하는 경우, 반드시 Enum 상수 목록과 메소드 정의 사이에 세미콜론(;)을 넣어줘야 함

entries는 컬렉션 타입이므로, map, filter, find 등을 사용해서 여러 커스텀 메소드를 작성하기 용이합니다.

TabGenreType.getGenreById(1) // VARIETY
TabGenreType.getGenreTitles() // [드라마, 예능, 영화, 스포츠, 애니메이션, 뉴스]

식으로 활용해줄 수 있습니다.

예시를 하나 더 살펴보자면, 앱에서 회원가입 시에 아이디, 비밀번호, 닉네임 입력 등의 과정이 별개의 화면으로 나눠져 있다면 이런 식으로도 Enum 클래스를 작성해줄 수 있습니다.

enum class SignUpStep(var order: Int) {
    ID(1),
    PASSWORD(2)
  	NICKNAME(3);

    companion object {
        fun getPrevStep(currentStep: SignUpStep): SignUpStep? { // 뒤로가기
            return entries.find {
                it.order == currentStep.order - 1
            }
        }

        fun getNextStep(currentStep: SignUpStep): SignUpStep? { // 다음
            return entries.find {
                it.order == currentStep.order + 1
            }
        }
    }
}

order 대신 Enum 클래스의 기본 프로퍼티인 ordinal을 사용하게끔 해도 무방합니다.

마지막으로 예제로 앱 내에 Enum 클래스로 색상을 저장해서 사용한 예시인데, 제가 예전에 작성한 [Android/Kotlin] 앱 내부에 서비스 색상 저장해서 사용하기 글을 참고해보시면 좋을 것 같습니다.

📚 참고 자료

profile
안드로이드 개발자를 꿈꾸는 학생입니다

0개의 댓글