본문 바로가기
프로그래밍/kotlin

[kotlin] 코틀린 차곡차곡 - 18. value class (inline class)

by 사바라다 2023. 2. 4.

안녕하세요. 오늘은 오랫만에 코틀린 알아보는 포스팅으로 찾아왔습니다. 오늘 여러분들에게 소개해드릴 코틀린 개념은 value class입니다. 자 그럼 바로 들어가보도록 하겠습니다.

상황 설명

유저 정보를 담는 Entity를 만들어보도록 하겠습니다. email, hashedPassword, displayName, createdAt 필드를 가지도록합니다. 그리고 email은 email의 특정 형식이 있습니다. 하지만 User Entity에서는 String 형식으로 email을 받고 있기때문에 email 형식을 충족시키지 못하더라도 Entity 생성을 할 수 있습니다. 이러한 검증을 위해서 init을 이용할 수 있습니다. 그리고 email의 경우 domain과 id를 @ 기준으로 분리할 수 있기 때문에 해당 메서드도 만들 수 있습니다. 코드는 아래와 같습니다.

@Entity
class User(
    @Id
    val email: String,
    val hashedPassword: String,
    val displayName: String,
    val createdAt: Instant
) {
    init {
        if (!Pattern.matches("^[_a-z0-9-]+(.[_a-z0-9-]+)*@(?:\\w+\\.)+\\w+$", email)) {
            throw IllegalArgumentException("not match regex and email")
        }
    }

    fun getId(): String {
        return value.split("@")[0]
    }

    fun getHost(): String {
        return value.split("@")[1]
    }
}

현재 추가한 검증(validation)과 method는 모두 email을 위한 것입니다. 이 경우 우리는 Email을 하나의 객체로 만들어서 User의 Email에 대해서 응집성을 높일 수 있습니다. 아래와 같은 클래스를 만들면 되는 것입니다.

class Email(val value: String) {
    init {
        if (!Pattern.matches("^[_a-z0-9-]+(.[_a-z0-9-]+)*@(?:\\w+\\.)+\\w+$", value)) {
            throw IllegalArgumentException("not match regex and email")
        }
    }

    fun getId(): String {
        return value.split("@")[0]
    }

    fun getHost(): String {
        return value.split("@")[1]
    }
}

코틀린에서는 이럴 경우 사용하기 좀 더 성능적으로 그리고 메모리적으로 이득인 class를 언어레벨에서 지원하고 있습니다. 이게 바로 value 클래스입니다.

Value Class

kotlin의 value 클래스는 Runtime에 해당 객체를 사용하는 객체에 property로 변환되는 클래스입니다. 따라서 연관있는 클래스에 대해서 응집도를 높일 수 있고 메모리, 그리고 성능적으로 효율을 모두 가져갈 수 있는 방법입니다. value class를 사용하면 위의 클래스는 아래 클래스처럼 바꿀 수 있습니다.

@Entity
class User(
    @Id
    val email: Email,
    val hashedPassword: String,
    val displayName: String,
    val createdAt: Instant
) {

    @JvmInline
    value class Email(val value: String) {
        init {
            if (!Pattern.matches("^[_a-z0-9-]+(.[_a-z0-9-]+)*@(?:\\w+\\.)+\\w+$", value)) {
                throw IllegalArgumentException("not match regex and email")
            }
        }

        fun getId(): String {
            return value.split("@")[0]
        }

        fun getHost(): String {
            return value.split("@")[1]
        }
    }
}

value 클래스의 특징은 아래와 같습니다.

  • value 클래스는 단 1개의 내부 필드만을 가질 수 있습니다. 여러개의 필드를 가지려고하면 코틀린 컴파일에서 에러가 발생합니다.
  • init block을 선언할 수 있습니다
  • 내부 메서드를 선언하고 사용할 수 있습니다
  • @JvmInline은 필수로 붙여야합니다.

그리고 컴파일 시 바뀌게 되는 Java 클래스는 아래와 같습니다. value class인 email 필드의 내부 필드인 value가 그대로 User의 필드로 내려온 것을 알 수 있습니다.

public class User {
   @Id
   @NotNull
   private final String email;

   @NotNull
   private final String hashedPassword;

   @NotNull
   private final String displayName;

   @NotNull
   private final Instant createdAt;

[...이하 생략...]

value class 사용방법 주의사항

value 클래스를 사용할 때도 일반 클래스를 사용하는 것과 완전 동일하게 사용할수는 없습니다. 몇가지 주의사항을 짚고 넘어가도록 하겠습니다. 클래스의 기본형태만을 사용할 수 있다고 봐주시면 될 것 같습니다.

  1. backing 필드를 가질 수 없습니다.
  2. lateinit 필드를 가질 수 없습니다.
  3. interface는 사용가능하나 상속은 불가능합니다.

부가 정보

참고로 1.4.x 버전까지는 value class가 아닌 inline class로써 keyword를 사용했습니다. 하지만 이 keyword는 1.5.x 버전이 되면서 value로 변경되었습니다. 이유는 kotlin에서는 inline function 이 별도로 존재합니다. 이것과 keyword가 동일했지만 내부 컨셉은 완전히 다르기 때문에 혼란이 야기되었습니다. 따라서 변경되었다고 합니다.

Test

마지막으로 테스트 코드로 테스트를 작성해보고 마무리하도록 하겠습니다.

@Test
fun success_when_email_is_normal() { // 성공 케이스
    // when & then
    assertDoesNotThrow {
        User(
            email = User.UserEmail("koangho93@naver.com"),
            createdAt = Instant.now(),
            displayName = "sabarada",
            hashedPassword = "snfksdvlnsdlkn",
        )
    }
}

@Test
fun fail_when_email_is_abnormal() { // 실패 케이스

    assertThrows<IllegalArgumentException> {
        User(
            email = User.UserEmail("koangho93"),
            createdAt = Instant.now(),
            displayName = "sabarada",
            hashedPassword = "snfksdvlnsdlkn",
        )
    }
}

마무리

오늘은 이렇게 kotlin의 value에 대해서 알아보는 시간을 가져보았습니다.

감사합니다.

참조

[1] https://kotlinlang.org/docs/inline-classes.html

[2] https://github.com/Kotlin/KEEP/blob/master/notes/value-classes.md#built-in-primitive-value-classes

댓글