TL;DR
- 引数の型パラメータのみ異なるような同名の関数は定義できない
-
same JVM signature
エラーが発生する
-
-
JvmName
を利用することで、Kotlin
での見た目上のオーバーロードを実現できる - ただし、幾つかのパターンでは単純に
JvmName
を付与しても問題が出るため、状況に応じた対処が必要- 継承可能な関数の場合
- 引数にラムダが絡む場合
オーバーロードとジェネリクス
Kotlin
(Java
)では、同名で引数の型が異なる関数を定義できます。
一方、List<Int>
とList<Long>
のように、ジェネリクスの型引数だけ異なるような関数は定義できません。
The following declarations have the same JVM signature...
としてコンパイルエラーとなります。
fun foo(intList: List<Int>)
fun foo(longList: List<Long>)
JvmNameを使った対処法
Kotlin
では、このようなシグネチャ競合エラーに対応するための機能として、JvmName
アノテーションが提供されています。
このアノテーションで名前を指定した場合、JVM
上での名前が別途付けられるため、シグネチャ競合エラーを回避しつつKotlin
上の見た目はオーバーロードされた状況にすることができます。
これを使うと、先ほどのサンプルコードは以下のようにできます。
@JvmName("fooInt")
fun foo(intList: List<Int>)
fun foo(longList: List<Long>)
単にJvmNameを付けるだけでは問題が出る場合について
JvmName
は便利な機能ですが、そのまま付けるだけだと問題が出る場合が有ります。
以下、それらの問題が出る場面と回避方法をまとめます1。
継承可能な関数の場合
インターフェースや抽象関数など、継承可能な関数にJvmName
アノテーションを付与した場合、'@JvmName' annotation is not applicable to this declaration
としてコンパイルエラーになります。
例えば以下のような定義全てでコンパイルエラーになります。
open class Foo {
@JvmName("fooInt")
open fun foo(intList: List<Int>) { /* ... */ }
}
abstract class Bar {
@JvmName("fooInt")
abstract fun foo(intList: List<Int>)
}
interface Baz {
// デフォルト実装が有ってもエラーになる
@JvmName("fooInt")
fun foo(intList: List<Int>) { /* ... */ }
}
このエラーはINAPPLICABLE_JVM_NAME
を抑制することで回避できます。
open class Foo {
@Suppress("INAPPLICABLE_JVM_NAME")
@JvmName("fooInt")
open fun foo(intList: List<Int>) { /* ... */ }
}
abstract class Bar {
@Suppress("INAPPLICABLE_JVM_NAME")
@JvmName("fooInt")
abstract fun foo(intList: List<Int>)
}
interface Baz {
@Suppress("INAPPLICABLE_JVM_NAME")
@JvmName("fooInt")
fun foo(intList: List<Int>) { /* ... */ }
}
補足
INAPPLICABLE_JVM_NAME
を抑制した場合、Java
との相互運用時に問題が生じる可能性があります(Kotlin
のみで運用する場合には気にしなくても大丈夫です)。
また、このような場合に関しては、今後新しくBinarySignatureName
アノテーションが導入され、抑制無しで扱えるようにする計画が有るそうです。
引数にラムダが絡む場合
引数にラムダが絡む場合も、JvmName
アノテーションでコンパイルを通すことはできます。
@JvmName("intFoo")
fun foo(intSupplier: () -> Int) {}
fun foo(longSupplier: () -> Long) {}
ただし、この関数を素直に呼び出すと、例え戻り値の型を指定したとしてもOverload resolution ambiguity. All these functions match.
としてコンパイルエラーになります。
fun bar() {
// -> Overload resolution ambiguity. All these functions match.としてエラーになる
foo { 1 }
}
この問題はOverloadResolutionByLambdaReturnType
アノテーションを用いることで抑制できます。
先ほどの定義を以下のようにすることで、呼び出すコードはコンパイルエラーにならなくなります。
@OptIn(ExperimentalTypeInference::class) // 実験的機能なためOptInが必要
@OverloadResolutionByLambdaReturnType
@JvmName("intFoo")
fun foo(intSupplier: () -> Int) {}
fun foo(longSupplier: () -> Long) {}
補足
引数をキャストすることでもこの問題は回避できますが、これでは不細工なので、問題が起きない間はOverloadResolutionByLambdaReturnType
を使っておく方がいいような気がしています。
fun bar() {
foo({ 1 } as () -> Int)
}
-
ここにまとめるのは筆者が把握している内容だけで、完全に網羅的とは限らないことをご容赦ください。 ↩