class Lotto(val lotto: List<LottoNumber>
다음과 같이 LottoNumber
리스트를 프로퍼티로 가지고 있는 Lotto
클래스가 있다.
참고로 LottoNumber
객체는 로또 범위 내의 숫자들을 미리 인스턴스화 해두었고 팩토리 함수를 통해 이를 리턴하는 형태이다.
이 경우, Lotto
객체를 생성하려면 다음과 같이 코드가 작성된다.
val lotto: Lotto = Lotto(listOf(1,2,3,4,5,6).map { LottoNumber.create(it) })
매번 생성할 때마다 다음과 같이 코드를 작성한다면
이는 일반 코드 뿐만 아니라 테스트 코드에서도 엄청난 가독성 방해(!?!)가 일어나게 될 것이고,
좀 더 간단하게 바꿔주고 싶은 강한 욕구가 든다.
constructor(numbers: List<Int>) : this(numbers.map { LottoNumber.create(it) })
이를 해결하기 위해 위와 같이 부생성자를 만들게 된다면 어떻게 될까?
Platform declaration clash: The following declarations have the same JVM signature ((Ljava/util/List;)V):
constructor Lotto(numbers: List) defined in lotto.model.Lotto
constructor Lotto(lotto: List) defined in lotto.model.Lotto
그렇다.. 다음과 같은 오류를 마주하게 된다. :(
이 오류가 무슨 의미냐면,
List< Int >와 List< LottoNumber >가 같은 JVM signature를 갖고있다는 의미다.
즉, 이 시그니처가 동일하기 때문에 코틀린 코드가 바이트코드로 변경될 때 오류가 발생하는 것이다.
이는 JVM compiler가 파라미터 타입으로 함수 또는 생성자를 구분할 때 generic type은 구분하지 못하기 때문이다.
List< Int >, List< String >, List< LottoNumber > 까지 <> 안에 무엇이 들어와도 에러가 발생한다.
이와 같이 JVM signature 오류가 발생한 경우, 보통 함수 앞에 @JvmName를 사용함으로써 해결할 수 있다.
@JvmName은 JVM signature를 변경할 때 사용하는 annotation이다.
// 예시
@JvmName("callFromString")
fun call(a: List<String> {}
@JvmName("callFromInt")
fun call(a: List<Int>) {}
하지만 @JvmName은 생성자에서는 사용할 수 없다.
이 경우, 이를 해결하기 위한 방법은 무엇이 있을까 ???
constructor(vararg numbers: Int): this(numbers.map { LottoNumber.create(it) })
list< Int > 대신 가변인자 vararg 를 사용할 수 있다.
생성할 때 int array를 넘기면 되는데, 이 때 배열 앞에 * 표시를 붙여주어야 한다.
companion object {
fun create(lotto: List<Int>) = Lotto(lotto.map { LottoNumber.create(it) }.sortedBy { it.toInt() })
}
companion object 객체를 활용하여 팩토리 함수를 정의한다.
constructor(
numbers: List<Int>,
@Suppress("UNUSED_PARAMETER") dummyImplicit: Any? = null
): this(numbers.map { LottoNumber.create(it) })
사용하지 않는 매개변수를 하나 추가하여 다른 시그니처가 생성되도록 한다.
더 많은 방법이 있을 수도 있겠지만 우선 위의 3가지 방법을 공부해보았다.
물론 무엇이 더 옳은지 나쁜지 정답은 없기에, 상황에 맞게 편한 방법으로 대처하면 될 것 같다 :)
참고 || 참고 할 만한 자료
링크
Kotlin annotation 정리
팩토리 함수