dely Tech Blog

クラシル・TRILLを運営するdely株式会社の開発ブログです

関数シグネチャーの衝突を回避する術

f:id:meilcli:20211130104105j:plain

こんにちは、えんじにゃーの@MeilCliです。猫よりペンギンのほうが好きです

今回はタイトルの通り名前の衝突を回避するテクニックを紹介したいと思います

@JvmName

Test.kt:

fun method(value: List<String>) {}

fun method(value: List<Int>) {}

ファイルのトップレベルにこういう関数を定義したくなったとします*1

これはJVMにおいてはシグネチャー(名前と引数の形)の衝突によってコンパイルエラーとなってしまいます。どういうことかというとListのジェネリクス要素のStringとIntはランタイム時にはKotlinで言うところのAnyとして解釈されるため、コンパイル時にエラーとして処理されているのです*2

@JvmName("methodB")
fun method(value: List<String>) {}

@JvmName("methodA")
fun method(value: List<Int>) {}

こういう場合は@JvmNameアノテーションを利用することで回避することができます。こうすることでJVM的にはこういう名前のメソッドとして扱ってくれよと指定できるのです

このKotlinコードのJavaから見えるシグネチャーは以下の感じになります

public final class TestKt {

    public static final void methodB(List<String> value) {}

    public static final void methodA(List<Integer> value) {}
}

JVM的に名前が違うメソッドになっているので前述のKotlin的には同名関数でもシグネチャーの衝突でエラーにはならないということです

interfaceでは@JvmNameを使えない

Test.kt:

interface Test {
    @JvmName("methodB")
    fun method(value: List<String>) {
    }

    @JvmName("methodA")
    fun method(value: List<Int>) {
    }
}

こんどはこのようなコードはどうでしょうか?

このコードをIntelliJ IDEAやAndroid Studioにかけると@JvmName annotation is not applicable to this declarationのようなエラーがでてしまいます。これはどうやらinterfaceでは多重継承・実装の際などに名前解決で問題になることがあるからこうなってるようです*3

実は使える

このエラーを回避するいいテクニックないかな〜と調べたところ、Suppressすることができるとのことでした

interface Test {
    @Suppress("INAPPLICABLE_JVM_NAME")
    @JvmName("methodB")
    fun method(value: List<String>) {
    }

    @Suppress("INAPPLICABLE_JVM_NAME")
    @JvmName("methodA")
    fun method(value: List<Int>) {
    }
}

このようにすることでコンパイルをすることができます。ただし、JavaなどのKotlin以外のJVM言語では前述の問題を引き起こす場合が出てくると思われるので使い所には注意が必要です(自分が確認した限りではKotlinで使う分には問題のない書き方になってるようにIDEによるチェックが効いてそうでした)*4

おわりに

さて、Javaとの互換性のために用意されてるようなアノテーションでJavaとの互換性を諦めるような書き方ができるのはそれはそれでいいのか🤔という感じがしてますが、困った際の小技としては有用なんじゃないかなと思います*5*6
それでも、使う場面としては限られているのと関数名を変える対処もできることから、同じ関数名を使いたくなるケース(たとえばKotlin専用のライブラリー開発)ぐらいでしか使うことはないんじゃないかなとも思うところです

まとめ:

  • シグネチャーが衝突したら@JvmNameを使うといいよ
  • @JvmNameが使えない場面でもKotlinのみで利用するなら@Suppress("INAPPLICABLE_JVM_NAME")が使えるかもよ

*1:そんなことないよというのはなしでお願いします

*2:C#ならそんなことないのでジェネリクスでお困りの諸氏にはC#をおすすめします

*3:https://youtrack.jetbrains.com/issue/KT-31420#focus=Comments-27-4211763.0-0

*4:使う際には自己責任でお願いします

*5:使う際にはほんとに自己責任でお願いします

*6:ちなみにこの技を発見したMeilCliはその場では関数名を変えることによって対処しました