언어/Kotlin

JvmName과 IntelliJ IDEA의 함수 검색, 수정 기능 관련 테스트

원석💎-dev 2025. 3. 16. 20:02
반응형

코틀린을 사용하게되면, 확장함수를 사용하고 싶어진다. 코드가 코틀린스럽고, 깔끔해지기 때문이다.

간단하게, 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의 함수 검색과 수정 기능에 대한 테스트를 해봤다.

반응형