PC パソコン

Kotlin「Overload resolution ambiguity」の原因と解決方法

Kotlinでコードを記述していると、「Overload resolution ambiguity」というエラーが表示されることがあります。

このエラーは、同じ名前の関数やプロパティが複数存在し、Kotlinのコンパイラがどれを使用すればよいのか判断できない場合などに発生します。

Kotlinでは、引数の型や数などによって同じ名前の関数を使い分けるオーバーロードが利用できますが、呼び出し方によって候補を一つに絞れない場合はエラーになります。

この記事では、Kotlinで「Overload resolution ambiguity」が表示される主な原因と、基本的な解決方法を紹介します。

「Overload resolution ambiguity」とは

「Overload resolution ambiguity」は、複数の候補が存在するため、どの関数やプロパティを使用するのか決定できないことを示すエラーです。

Kotlinでは、同じ名前で引数の型や数が異なる関数を定義できます。

fun show(value: Int) {
    println("Int: $value")
}

fun show(value: String) {
    println("String: $value")
}

このような定義では、show(10)であればIntを受け取る関数、show("Hello")であればStringを受け取る関数が選択されます。

しかし、呼び出し時の情報だけでは複数の候補のどちらを使うべきか判断できない場合、「Overload resolution ambiguity」が表示されます。

原因1:同じ名前の関数に複数の候補がある

もっとも基本的な原因は、同じ名前の関数が複数存在し、引数からどの関数を呼び出すべきか判断できないことです。

fun printValue(value: String?) {
    println(value)
}

fun printValue(value: Int?) {
    println(value)
}

fun main() {
    printValue(null)
}

この例では、String?を受け取るprintValue()と、Int?を受け取るprintValue()があります。

nullだけを渡した場合、どちらのnullable型にも渡せるため、コンパイラはどちらを使用するべきか判断できません。

このような場合は、型を明示して候補を一つに絞ります。

fun main() {
    printValue(null as String?)
}

これにより、String?を受け取る関数が使用されることが明確になります。

原因2:nullを引数として渡している

オーバーロードされた関数の引数にnullable型が複数存在する場合、nullを直接渡すことで曖昧になることがあります。

fun process(value: String?) {
    println("String")
}

fun process(value: List<String>?) {
    println("List")
}

fun main() {
    process(null)
}

nullString?にもList<String>?にも代入できるため、どちらのprocess()を使用するか決定できません。

変数へ型を付けてから渡す方法でも解決できます。

fun main() {
    val value: String? = null

    process(value)
}

この場合、valueの型がString?であることが分かるため、対応する関数が選択されます。

原因3:ラムダ式の型を判断できない

オーバーロードされた関数がラムダ式を引数として受け取る場合、ラムダ式だけではどの関数型を使用するのか判断できないことがあります。

fun execute(action: (Int) -> Unit) {
    action(1)
}

fun execute(action: (String) -> Unit) {
    action("Hello")
}

このように似た関数型を受け取るオーバーロードが存在すると、呼び出し方によってはコンパイラがラムダ式の型を一つに決定できない場合があります。

その場合は、ラムダ式の引数型を明示します。

execute { value: Int ->
    println(value)
}

ラムダ式の引数をIntと明示することで、(Int) -> Unitを受け取る関数を使用することが分かります。

原因4:メソッド参照の候補が複数ある

関数参照やメソッド参照を使用している場合も、同じ名前の関数が複数あると参照先が曖昧になることがあります。

fun convert(value: Int): String {
    return value.toString()
}

fun convert(value: Double): String {
    return value.toString()
}

このように複数のconvert()がある場合、単に::convertと記述しただけでは、使用される文脈によってはどちらを参照するのか判断できないことがあります。

その場合は、受け取る関数型を明示します。

val converter: (Int) -> String = ::convert

このように関数型を明示することで、Intを受け取るconvert()が選択されます。

原因5:importによって同じ名前の関数が複数存在する

異なるパッケージから同じ名前の関数をimportしている場合にも、呼び出し先が曖昧になることがあります。

import packageA.calculate
import packageB.calculate

fun main() {
    calculate()
}

両方のパッケージに同じ条件で呼び出せるcalculate()が存在すると、どちらを使用するのか判断できません。

このような場合は、完全修飾名を使用することで呼び出し先を明確にできます。

fun main() {
    packageA.calculate()
}

また、import時に別名を付ける方法もあります。

import packageA.calculate as calculateA
import packageB.calculate as calculateB

fun main() {
    calculateA()
}

原因6:拡張関数の候補が複数ある

Kotlinでは拡張関数を定義できますが、同じ名前で利用可能な拡張関数が複数存在すると、呼び出し先が曖昧になる場合があります。

fun String.display() {
    println(this)
}

同じスコープやimportの関係によって、同じ型に対して使用可能な同名の拡張関数が複数存在すると、コンパイラが候補を一つに絞れないことがあります。

この場合は、不要なimportを削除したり、別名importを使用したりして、利用する関数を明確にします。

原因7:数値リテラルの型が曖昧になっている

数値を受け取るオーバーロードが複数ある場合、型の推論結果によって呼び出し先が分かりにくくなることがあります。

fun calculate(value: Long) {
    println("Long")
}

fun calculate(value: Double) {
    println("Double")
}

このような場合は、必要に応じて数値リテラルや変数の型を明示すると、どの関数を使用するのか分かりやすくなります。

fun main() {
    calculate(10L)
    calculate(10.0)
}

10LLong10.0Doubleとして扱われるため、それぞれ対応する関数が選択されます。

引数の型を明示して解決する

「Overload resolution ambiguity」が表示された場合、もっとも基本的な解決方法の一つは、引数や変数の型を明示することです。

val value: String? = null

printValue(value)

型を明示することで、コンパイラが利用可能な候補を絞り込めるようになります。

特に、null、ラムダ式、関数参照などは、それだけでは型を判断しにくい場合があるため、型を明示することで解決できることがあります。

不要なオーバーロードを見直す

同じ名前の関数を多数オーバーロードすると、呼び出し側で曖昧さが発生しやすくなる場合があります。

似た型やnullable型を多数受け取る関数を作成している場合は、本当に同じ関数名にする必要があるかを確認することも重要です。

エラーを呼び出し側だけで回避するのではなく、オーバーロードそのものが分かりにくい設計になっていないか確認することも有効です。

不要なimportを確認する

自分で同じ名前の関数を定義していない場合でも、importされた関数や拡張関数が原因で「Overload resolution ambiguity」が発生することがあります。

Android StudioなどのIDEでは、自動importによって意図していない関数が追加されることもあります。

エラー箇所だけでなく、ファイル上部のimportも確認し、同じ名前の関数や拡張関数が複数読み込まれていないか確認します。

Overload resolution ambiguityが表示されたときの確認ポイント

「Overload resolution ambiguity」が表示された場合は、エラーになっている関数やプロパティについて、コンパイラから見て候補が複数存在していないか確認します。

  • 同じ名前のオーバーロードされた関数が複数存在していないか
  • nullを直接引数として渡していないか
  • ラムダ式の引数型が曖昧になっていないか
  • 関数参照やメソッド参照の候補が複数存在していないか
  • 異なるパッケージから同じ名前の関数をimportしていないか
  • 同じ名前の拡張関数が複数利用可能になっていないか
  • 引数や変数の型を明示することで候補を一つに絞れないか

特に、エラーが突然発生した場合は、コードそのものだけではなく、追加されたimportや新しく定義したオーバーロードも確認すると原因を見つけやすくなります。

まとめ

Kotlinの「Overload resolution ambiguity」は、同じ名前の関数やプロパティなどに複数の候補が存在し、コンパイラがどれを使用するべきか一つに決定できない場合に表示されるエラーです。

代表的な原因には、オーバーロードされた関数へnullを渡している場合、ラムダ式や関数参照の型が曖昧な場合、同じ名前の関数や拡張関数を複数importしている場合などがあります。

基本的には、引数や変数の型を明示する、関数型を明示する、完全修飾名や別名importを使用するなどの方法で、使用する候補を一つに絞ることで解決できます。

また、オーバーロードの数が多すぎる場合や似た型の関数が多数存在する場合は、関数の設計そのものを見直すことも有効です。

エラーが表示されたときは、呼び出し部分だけを見るのではなく、同名の関数、引数の型、ラムダ式、関数参照、importなどを確認し、「コンパイラから見て候補が一つに決まるか」を確認すると原因を見つけやすくなります。