4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-10-15

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. ここにまとめるのは筆者が把握している内容だけで、完全に網羅的とは限らないことをご容赦ください。

4
3
0

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
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?