Enum class란 일반적으로 선언되는 상수와는 다르게, 이름과 동일하게 여러 개의 값을 열거해서 사용할 수 있도록 정의해 놓은 클래스이다.
enum class Fruit{
GRAPE,
APPLE,
ORANGE,
MANGE
}
일반적인 형태의 enum class는 위와 같은 형태로,
Fruit가 해당 클래스의 클래스 명이며, 블록 안에 순차적으로 콤마(,)를 통해 나열된 값이
해당 클래스의 인스턴스이다.
특별히 생성자를 정의하지 않는 경우 위에서부터 순차적으로 0,1,2,3...의 값이 부여된다.
각 enum 인스턴스는 일반적으로 name과 ordinal이라는 프로퍼티를 가지며,
name은 인스턴스의 이름을,ordinal은 인스턴스의 순서를 나타낸다.
위와 같이 선언된 enum class는 다음과 같이 사용될 수 있다.
val fruit = Fruit.GRAPE
fruit.name // "GRAPE"
fruit.ordinal // 0
0부터 시작하는 정수 값 이외에도 다른 값을 넣을 수도 있는데,이런 경우 별도의 생성자를 정의해주어야 한다.
enum class Fruit(price:Int,tag:String){
GRAPE(3000,"grape"),
APPLE(2000,"apple"),
ORANGE(4000,"orange"),
MANGO(10000,"mango")
}
생성자를 가진 enum 클래스의 인스턴스는 동일한 생성자로 값을 넣어야 하고,
같은 클래스 내에 동일한 값을 가진 인스턴스는 존재할 수 없음에 주의해야 한다.
이렇게 생성된 클래스는 다음과 같이 사용될 수 있다.
val fruit = Fruit.GRAPE
fruit.price // 3000
fruit.tag // "grape"
enum 클래스도 클래스이기에, 일반적인 클래스의 기능도 사용할 수 있다.
함수 및 프로퍼티도 추가할 수 있다.
enum class Fruit(price:Int,tag:String){
GRAPE(3000,"grape"),
APPLE(2000,"apple"),
ORANGE(4000,"orange"),
MANGO(10000,"mango");
fun printName():String = "fruit name is $name"
val tenPiecePrice
get() = price*10
}
위와 같이 함수를 정의하면, 해당 printName()이라는 함수는 각 인스턴스별로 동일하게 사용할 수 있다.단, 함수를 정의할 때에는 마지막으로 선언된 인스턴스에 세미콜론(;)을 붙여 분리해야 한다.
val fruit = Fruit.GRAPE
fruit.printName() // "fruit name is GRAPE"
fruit.tenPiecePrice // 30000
추상 메소드도 정의 가능하다. 해당 메소드를 정의하면 각 인스턴스마다 메소드의 내용을 달리 정의할 수 있다.
enum class Fruit(price:Int,tag:String){
GRAPE(3000,"grape"){
override fun printName():String = "fruit name is grape"
},
APPLE(2000,"apple"){
override fun printName():String = "fruit name is apple"
},
ORANGE(4000,"orange"){
override fun printName():String = "fruit name is orange"
},
MANGO(10000,"mango"){
override fun printName():String = "fruit name is mango"
};
abstract fun printName():String
}
위와 같이 추상메소드를 정의하는 대신 인터페이스를 상속하여 구현할 수도 있다.
interface Printable(){
fun printName():String
}
enum class Fruit(price:Int,tag:String):Printable{
GRAPE(3000,"grape"){
override fun printName():String = "fruit name is grape"
},
APPLE(2000,"apple"){
override fun printName():String = "fruit name is apple"
},
ORANGE(4000,"orange"){
override fun printName():String = "fruit name is orange"
},
MANGO(10000,"mango"){
override fun printName():String = "fruit name is mango"
};
}
개인적으로는 enum 클래스에 추상 메소드를 정의하는 것 보다는, 인터페이스를 상속하여 구현하는 것이 훨씬 더 간편할 것으로 보인다.
여기서 주의할 점은, enum 클래스는 인터페이스는 구현할 수 있으나 일반 클래스를 상속하는 것은 불가능 하다는 것을 알아야 한다.
마찬가지로, enum class 또한 다른 클래스에게 상속될 수 없다.
이외에도 enum 클래스는 다른 유용한 메소드를 제공한다.
valueOf(value:String): value와 일치하는 이름을 가진 enum 인스턴스 값을 반환한다.
values() : 해당 Enum 클래스에 포함된 값들을 Array로 반환한다.
필자는 enum class를 일반적으로 쓰이는 상수값을 변수처럼 이름을 붙여놓은 것처럼 생각한다.
특정 상수를 val 변수에 할당하여 이름붙여 사용하면 재사용성이 증가하고 관리포인트가 줄어들어 유지보수성이 편리해진다는 장점이 있다.그러한 상수가 여러개 생기게 되면 어떠한 상수가 있는지, 몇 개의 상수가 있는지 개발자의 기억력에 의존하게 된다는 문제가 있다.
상수를 여러 개 쓰다보면 각 상수마다 비슷한 용도 및 맥락에서 사용되는 경우가 있는데,
이러한 상수를 enum 클래스를 사용하여 묶어두면 로직 작성 및 유지보수하는 관점에서도 편리해진다.
그 중 하나가 when 조건문과 함께하는 경우인데,
when 문에서는 일반적으로 매번 else 문을 써줘야 하는 경우가 많다.
이러한 경우 enum 값을 사용하면 이러한 문제가 깔끔해진다.
val value:Fruit = Fruit.GRAPE
when(value){
Fruit.GRAPE-> {...}
Fruit.APPLE-> {...}
Fruit.MANGO-> {...}
Fruit.ORANGE-> {...}
}
위 문에서는 enum 클래스인 Fruit의 모든 인스턴스를 명시했기 때문에,
컴파일 시점에서 해당 값들의 한계를 명확히 알 수 없으므로,
따로 else 문을 써주지 않아도 된다.
이외에도 UI를 유지보수하기 편하게 하기 위해 State 패턴을 이용하는 경우가 있는데,
이러한 상태 객체를 정의하기 위해 enum class를 사용할 수 있다.
enum class UiState(){
Success,
Fail,
Loading
}
fun handleState(state:UiState){
when(state){
Success-> { ...}
Fail -> { ...}
Loading -> { ...}
}
}
위와 같이 특정 Ui의 상태값을 받고서 State의 상태에 따라서 when문에 따라 분기하여
해당 값을 처리할 수 있도록 할 수도 있다.
enum class의 개념과 그 용법에 대해 알아보았다.
코틀린에서는 enum class와 sealed class가 비교되어 사용되는 경우가 많은데,
다음에서는 해당 부분에 대해 다루어 볼 예정이다.
https://kotlinworld.com/83
https://www.devkuma.com/docs/kotlin/enum-classes/
https://7942yongdae.tistory.com/185