search
LoginSignup
0
Help us understand the problem. What are the problem?

posted at

updated at

Organization

【Kotlin】JvmNameを使ったsame JVM signatureエラー回避テクニックまとめ

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上の見た目はオーバーロードされた状況にすることができます。

これを使うと、先ほどのサンプルコードは以下のようにできます。

List< Int>を引数に取る方に名前を付けた例
@JvmName("fooInt")
fun foo(intList: List<Int>)
fun foo(longList: List<Long>)

単にJvmNameを付けるだけでは問題が出る場合について

JvmNameは便利な機能ですが、そのまま付けるだけだと問題が出る場合が有ります。
以下、それらの問題が出る場面と回避方法をまとめます1

継承可能な関数の場合

インターフェースや抽象関数など、継承可能な関数にJvmNameアノテーションを付与した場合、'@JvmName' annotation is not applicable to this declarationとしてコンパイルエラーになります。
例えば以下のような定義全てでコンパイルエラーになります。

単純なJvmNameで回避できない例(List< Long>分の定義は省略)
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を抑制することで回避できます。

エラーを抑制して回避した例(List< Long>分の定義は省略)
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アノテーションを用いることで抑制できます。

先ほどの定義を以下のようにすることで、呼び出すコードはコンパイルエラーにならなくなります。

OverloadResolutionByLambdaReturnTypeで抑制した例
@OptIn(ExperimentalTypeInference::class) // 実験的機能なためOptInが必要
@OverloadResolutionByLambdaReturnType
@JvmName("intFoo")
fun foo(intSupplier: () -> Int) {}
fun foo(longSupplier: () -> Long) {}

補足

引数をキャストすることでもこの問題は回避できますが、これでは不細工なので、問題が起きない間はOverloadResolutionByLambdaReturnTypeを使っておく方がいいような気がしています。

ブロックをキャストしてエラーを回避した例
fun bar() {
    foo({ 1 } as () -> Int)
}
  1. ここにまとめるのは筆者が把握している内容だけで、完全に網羅的とは限らないことをご容赦ください。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0
Help us understand the problem. What are the problem?