본문 바로가기
Language/Kotlin

[Kotlin IN ACTION] - Kotlin sealed 클래스

by Jman 2022. 7. 25.

봉인된 클래스 : 클래스 계층 정의시 계층 확장 제한

예제 코드를 보고 설명을 하겠다.

interface Expr

class Num (val value : Int) : Expr
class Sum (val left : Expr, val right : Expr) : Expr

fun eval (e : Expr) : Int =
	when(e) {
    	is Num -> e.value
        is Sum -> eval(e.right) + eval(e.left)
        else ->
        	throw IllegalArgumentException("Unknown expression")
    }

 

위 코드를 확인해보면, 우리가 eval 함수 내에서 작성한 when 식에서 else 라는 분기를 반드시 넣어주어야 한다는 사실을 알 것이다.

코틀린 컴파일러는 when을 사용해 Expr 타입의 값을 검사할 때, 꼭 디폴트 분기인 else 분기를 덧붙이게 강제한다.

 

항상 디폴트 분기를 추가하는 게 편하지는 않다.

그리고 디폴트 분위기가 있으면 이런 클래스 계층에 새로운 하위 클래스를 추가하더라도 컴파일러가 when이 모든 경우를 처리하는 지 제대로 검사가 되지 않는다.

 

혹, 실수로 새로운 클래스 처리를 잊어버렸더라도 디폴트 분기가 선택되기 때문에 심각한 버그가 발생할 수 있다.

 

이럴 때, 코틀린에서 이런 문제를 해결하기 위해 sealed 클래스 만들었다.

 

sealed 클래스

상위 클래스에서 sealed 변경자를 붙이면 그 상위 클래스를 상속한 하위 클래스 정의를 제한할 수 있다.

sealed 클래스의 하위 클래스를 정의할 때는 반드시 상위 클래스 안에서 중첩 시켜야한다.

 

// sealed 클래스로 식을 표현하기
sealed class Expr { // 기반 클래스를 sealed 로 봉인한다.

	// 기반 클래스의 모든 하위 클래스를 중첩 클래스로 나열한다.
	class Num(val value : Int) : Expr() 
    class Sum(val left : Expr, val right : Expr) : Expr()
}

fun eval(e : Expr) : Int =
	when(e) { // when 식이 모든 하위 클래스를 검사하므로 별도의 "else" 분기가 없어도 됨
    	is Expr.Num -> e.value
        is Expr.Sum -> eval(e.right) + eval(e.left)
    }

 

when 식에서 sealed 클래스의 모든 하위 클래스를 처리한다면 디폴트 분기(else 분기) 가 필요 없다. sealed로 표시된 클래스는 자동으로 open임을 기억 해야한다.

따라서, 별도로 open 변경자를 붙일 필요가 없다. 

 

Kotlin IN ACTION 157p

 

sealed 클래스에 속한 값에 대해 디폴트 분기를 사용하지 않고 when 시글 사용하면 나중에 sealed 클래스의 상속 계층에 새로운 하위 클래스를 추가해도 when 식이 컴파일되지 않는다. 

따라서, 식을 고쳐야 한다는 사실을 쉽게 알 수 있다.