JvmName과 IntelliJ IDEA의 함수 검색, 수정 기능 관련 테스트
코틀린을 사용하게되면, 확장함수를 사용하고 싶어진다. 코드가 코틀린스럽고, 깔끔해지기 때문이다.
간단하게, Int 타입의 List를 받아서 각 항목이 Map의 key, value가 되는 코드를 작성해보자.
1. 함수 사용
fun main() {
val intList: List<Int> = listOf(1, 2, 3, 4)
val map: Map<Int, Int> = getKeyValueMapFrom(intList)
map.forEach { println(it) }
}
private fun getKeyValueMapFrom(list: List<Int>): Map<Int, Int> = list.associateWith { it }
2. 확장 함수 사용
fun main() {
val map: Map<Int, Int> = listOf(1, 2, 3, 4).toKeyValueMap()
map.forEach { println(it) }
}
private fun List<Int>.toKeyValueMap(): Map<Int, Int> = this.associateWith { it }
결과는 동일하지만 확장함수를 사용해 코드가 조금 더 깔끔해졌다. 하지만, 확장 함수가 추가되면 문제가 발생한다.
String 타입의 List를 받아서 각 항목이 Map의 key, value가 되는 코드를 추가로 작성해보자.
3. 확장 함수 추가
fun main() {
val map1: Map<Int, Int> = listOf(1, 2, 3, 4).toKeyValueMap()
map1.forEach { println(it) }
val map2: Map<String, String> = listOf("1", "2", "3", "4").toKeyValueMap()
map2.forEach { println(it) }
}
private fun List<Int>.toKeyValueMap(): Map<Int, Int> = this.associateWith { it }
private fun List<String>.toKeyValueMap(): Map<String, String> = this.associateWith { it }
위 코드는 빌드 시점에 오류가 발생한다.
타입 이레이저로 List<Int>와 List<String>은 동일한 List로 컴파일되기 때문이다.
결과적으로 바이트코드에는 List.toKeyValueMap(): Map<Int, Int>, List.toKeyValueMap(): Map<String, String>이 존재하게 된다. 확장함수 명이 동일하므로, 오류가 발생한다.
4. JVMName 추가
JVMName 어노테이션을 추가하면 바이트코드의 명칭을 바꿔주기 때문에, toKeyValueMap이라는 함수명을 사용할 수 있다.
fun main() {
println("map1::")
val map1: Map<Int, Int> = listOf(1, 2, 3, 4).toKeyValueMap()
map1.forEach { println(it) }
println("\nmap2::")
val map2: Map<String, String> = listOf("1", "2", "3", "4").toKeyValueMap()
map2.forEach { println(it) }
}
@JvmName("intListToKeyValueMap")
private fun List<Int>.toKeyValueMap(): Map<Int, Int> = this.associateWith { it }
@JvmName("stringListToKeyValueMap")
private fun List<String>.toKeyValueMap(): Map<String, String> = this.associateWith { it }
패키지를 새로 만들어 private 함수를 public으로 변경하고 여러군데서 import할 수 있도록 변경해보자. 그리고, Main 클래스를 여러개 만들어보자. 패키지 구조는 다음과 같다.
// ListExtension.kt
package org.example.extension
@JvmName("intListToKeyValueMap")
fun List<Int>.toKeyValueMap(): Map<String, String> = this.associate { it.toString() to it.toString() }
@JvmName("stringListToKeyValueMap")
fun List<String>.toKeyValueMap(): Map<String, String> = this.associateWith { it }
Main1, Main2, Main3이 동일하고, Main4, 5가 동일하다.
// Main1~3
package org.example
import org.example.extension.toKeyValueMap
fun main() {
println("map1::")
val map1: Map<String, String> = listOf(1, 2, 3, 4).toKeyValueMap()
map1.forEach { println(it) }
}
// Main4~5
package org.example
import org.example.extension.toKeyValueMap
fun main() {
println("map2::")
val map2: Map<String, String> = listOf("1", "2", "3", "4").toKeyValueMap()
map2.forEach { println(it) }
}
5. IDE의 기능 - 함수 사용위치 탐색, 수정에 대하여
IntelliJ IDEA의 기능으로 command+클릭(맥북기준)은 함수가 사용중인 위치를 탐색해준다.
분명 Main1, Main2, Main3은 List<Int>를 사용하고, Main4, Main5는 List<String>을 사용하고 있다. List<String>이 사용되는 부분은 Main4, Main5로 나오지만, import문은 전체 다 검색되는 것을 알 수 있다.
만약, List<String>.toKeyValueMap()을 수정한다면? List<String>을 사용하는 부분은 정확히 검색이 되지만, import되는 부분은 동일 한 이름의 함수를 모두 검색한다.
또한, IntelliJ의 또 다른 기능인 "이름변경"을 통해 수정한다면? 사용되는 부분만 변경을 해주는지? 모든 import까지 변경해줄지 기능에 대한 혼동이 생긴다.
toKeyValueMap2로 변경해보자. 변경 시 사용되는 부분의 import와 함수명만 바뀐것을 확인할 수 있다.
결론, JvmName과 IntelliJ IDEA의 함수 검색과 수정 기능에 대한 테스트를 해봤다.