enum class Direction {
NORTH, SOUTH, WEST, EAST
}
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
enum 상수는 객체이다. enum 상수는 쉼표로 구분된다.enum 상수는 해당 메서드와 재정의한 기본 메서드를 사용하여 고유한 익명 클래스를 선언할 수 있다.enum class ProtocolState {
WAITING {
override fun signal() = TALKING
},
TALKING {
override fun signal() = WAITING
}; // 세미콜론으로 구분
abstract fun signal(): ProtocolState // Enum이 추상클래스이기 때문에 가능한 일
}
enum이 추상클래스이기 때문에 interface 구현이 가능하다.enum class들은 Comparable interface를 기본적으로 implement한다. → enum class의 상수는 natural order로 정의된다.when 사용 시 모든 케이스를 커버한다면 else가 필요하지 않다.enum class Direction {
NORTH, SOUTH, WEST, EAST
}
fun main() {
val direction = Direction.NORTH
when(direction) {
Direction.NORTH -> println("북쪽!")
Direction.SOUTH -> println("남쪽!")
Direction.WEST -> println("동쪽!")
Direction.EAST -> println("서쪽!")
}
}
sealed interface Error
sealed class IOError(): Error
class FileReadError(val file: File): IOError()
class DatabaseError(val source: DataSource): IOError()
object RuntimeError : Error // 상태값이 바뀌지 않는 경우 사용
sealed class의 모든 subclass는 compile time에 알 수 있다. (컴파일러에서 sealed class의 자식 클래스가 어떤 것이 있는지 알 수 있음)protected(기본) 또는 privatewhen을 사용할 때!else가 필요하지 않다. → 첫번째 이유 때문fun log(e: Error) = when(e) {
is FileReadError -> { println("Error while reading file ${e.file}") }
is DatabaseError -> { println("Error while reading from database ${e.source}") }
is RuntimeError -> { println("Runtime error") }
// the `else` clause is not required because all the cases are covered
}
when 사용 시 else가 필요 없다.enum class의 서브 클래스를 생성할 수 없지만, sealed class는 가능하다.enum class에서는 각 enum 상수가 single instance로만 존재한다. (싱글톤) → 상수 정의 이후에 상태값을 변경할 수 없다.sealed class의 subclass는 각각 고유한 상태를 가진 multiple instance로 존재할 수 있다. → 상태값을 유동적으로 변경할 수 있다.fun readName(): List<String> {
val input = readln().replace(" ", "").split(",")
input.forEach {
verifyName(it) ?: return readName()
}
return input
}
private fun verifyName(name: String): String? {
return when {
name.isBlank() -> {
println("입력 값이 비었습니다")
null
}
!Regex("[가-힣a-zA-Z]+").matches(name) -> {
println("입력 값이 문자가 아닙니다")
null
}
else -> {
name
}
}
}
코드를 작성하면서 사용자 입력 에러를 관리해야할 일이 생겼다. null을 받으면 입력을 반복하게끔 해야하는데, depth가 깊어지고 라인 수도 길어져서 마음에 안드는 상황이었다. 이 때, sealed class를 활용하는 방법을 찾았다.
sealed class InputState<out T> { // generic 사용
class Success<out T>(val input: T) : InputState<T>()
class Error(val error: String) : InputState<Nothing>()
}
위와 같이 InputState를 정의해주었다. 예시로 제시한 코드에서는 String의 타입만 사용하기 때문에 사실 Generic을 사용하지 않아도 괜찮지만, 전체 코드에서는 String뿐만 아니라 다양한 타입을 입력받고 검증하고 있기 때문에 Generic을 적용해주었다.
fun readName(): List<String> {
val input = readln().replace(" ", "").split(",")
input.forEach {
verifyName(it) ?: return readName()
}
return input
}
private fun verifyName(name: String): String? {
val isError = when {
name.isBlank() -> InputState.Error("입력 값이 비었습니다")
!Regex("[가-힣a-zA-Z]+").matches(name) -> InputState.Error("입력 값이 문자가 아닙니다")
else -> InputState.Success(name) // InputState<String> 타입
}
if (isError is InputState.Error) {
println(isError.error)
return null
}
return name
}
다양한 에러 상황에 대해 알맞는 출력 문구를 넣어줄 수 있었다. 검증할 때 Error였다면 문구를 출력하고 null을 리턴하는 방식이다.
이렇게 sealed class는 상수 값을 갖는 enum과 달리 변경 가능한 값을 가지면서, 오직 객체 하나만을 가지는 enum과 달리 객체를 여러개 생성할 수 있으면서 enum과 비슷한 일을 해줄 수 있다. 값을 유동적으로 변동할 수 있다는 것이 핵심이다!