こんにちは、えんじにゃーの@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")
が使えるかもよ