PC パソコン

Kotlin「Conflicting overloads」の原因と解決方法

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

このエラーは、同じ名前の関数やコンストラクタなどが複数定義されており、Kotlinのコンパイラから見て区別できない場合などに発生します。

Kotlinでは、引数の型や数が異なれば同じ名前の関数を定義するオーバーロードが利用できますが、実質的に同じシグネチャとして扱われる定義が重複するとエラーになります。

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

「Conflicting overloads」とは

「Conflicting overloads」は、複数のオーバーロードが競合しており、コンパイラがそれらを別々の定義として扱えないことを示すエラーです。

例えば、同じ名前で同じ型の引数を持つ関数を2つ定義すると、どちらも同じ呼び出し方になるため競合します。

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

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

この場合、どちらのshow()Intを1つ受け取る同じ関数として扱われるため、「Conflicting overloads」が表示されます。

原因1:同じシグネチャの関数を重複して定義している

もっとも基本的な原因は、同じ名前で同じ引数型を持つ関数を複数定義していることです。

fun calculate(value: Int) {
    println(value)
}

fun calculate(value: Int) {
    println(value * 2)
}

この2つのcalculate()は、どちらもIntを1つ受け取るため区別できません。

異なる処理として必要な場合は、関数名を変更します。

fun calculate(value: Int) {
    println(value)
}

fun calculateDouble(value: Int) {
    println(value * 2)
}

また、同じ名前でオーバーロードしたい場合は、引数の型や数を変える必要があります。

fun calculate(value: Int) {
    println(value)
}

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

原因2:引数名だけを変えてオーバーロードしている

Kotlinでは、引数の名前が異なっていても、引数の型や数が同じであれば別のオーバーロードとしては扱われません。

fun printValue(number: Int) {
    println(number)
}

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

この例では、引数名はnumbervalueで異なりますが、どちらもIntを1つ受け取ります。

そのため、コンパイラから見ると同じシグネチャの関数となり競合します。

オーバーロードを区別するためには、引数名ではなく、引数の型や数などに違いが必要です。

原因3:戻り値の型だけが異なる関数を定義している

戻り値の型だけを変えても、Kotlinではオーバーロードを区別できません。

fun getValue(): Int {
    return 10
}

fun getValue(): String {
    return "10"
}

この場合、どちらも引数なしのgetValue()であり、呼び出し時の形が同じです。

戻り値の型だけでは区別できないため、「Conflicting overloads」が発生します。

異なる処理として必要な場合は、関数名を変更します。

fun getIntValue(): Int {
    return 10
}

fun getStringValue(): String {
    return "10"
}

原因4:デフォルト引数によって呼び出し方が競合している

デフォルト引数を使用している場合、定義そのものは異なっていても、同じ呼び出し方が成立して競合の原因になることがあります。

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

fun show(value: Int, message: String = "Hello") {
    println("$message: $value")
}

このような定義では、呼び出し方によって候補が重なる可能性があります。

デフォルト引数を利用する場合は、既存のオーバーロードと呼び出し方が重複していないか確認することが重要です。

原因5:同じクラス内でコンストラクタが競合している

関数だけでなく、コンストラクタでも同じ引数構成の定義が複数存在すると競合します。

class User {

    constructor(name: String) {
        println(name)
    }

    constructor(value: String) {
        println(value)
    }
}

引数名は異なりますが、どちらもStringを1つ受け取るコンストラクタです。

そのため、コンパイラはこれらを区別できません。

コンストラクタを複数用意する場合は、引数の型や数を変える必要があります。

class User {

    constructor(name: String) {
        println(name)
    }

    constructor(id: Int) {
        println(id)
    }
}

原因6:同じファイルや同じパッケージに同一関数が存在する

同じファイル内だけでなく、同じパッケージ内の別ファイルに同じトップレベル関数が定義されている場合も、「Conflicting overloads」が発生することがあります。

// FileA.kt
fun display(value: String) {
    println(value)
}

// FileB.kt
fun display(value: String) {
    println(value)
}

同じパッケージ内であれば、これらは同じ名前空間に存在するトップレベル関数として扱われます。

そのため、エラーになっているファイルだけではなく、同じパッケージ内の別ファイルに同名・同シグネチャの関数がないか確認する必要があります。

原因7:コピーしたコードが重複している

関数やクラスのコードをコピーした後、古い定義を削除せずに残していることが原因になる場合もあります。

特に、大きなファイルを編集した場合や、別ファイルへ処理を移動した場合は、同じ関数が複数残っていないか確認します。

「Conflicting overloads」が突然表示された場合は、直前にコピー、移動、リファクタリングした関数が重複していないか確認すると原因を見つけやすくなります。

原因8:拡張関数の定義が競合している

拡張関数でも、同じスコープ内で同じ型に対して同じ名前・同じ引数構成の関数を定義すると競合することがあります。

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

fun String.show() {
    println("Value: $this")
}

どちらもStringに対する引数なしのshow()であるため、同じ拡張関数として競合します。

処理を分ける必要がある場合は、関数名や引数構成を変更します。

原因9:ジェネリクスによって実質的に同じ定義になっている

ジェネリクスを使用している場合も、定義の仕方によってはコンパイラから見て競合することがあります。

fun <T> process(value: T) {
    println(value)
}

fun <R> process(value: R) {
    println(value)
}

型パラメータ名がTRで異なっていても、どちらも任意の型を1つ受け取る同じ構造の関数です。

そのため、型パラメータ名を変更しただけでは別のオーバーロードとして扱われません。

Conflicting overloadsとOverload resolution ambiguityの違い

「Conflicting overloads」と「Overload resolution ambiguity」は似ていますが、発生する状況が異なります。

「Conflicting overloads」は、関数などを定義した時点で、それらの定義自体が区別できず競合している場合に発生します。

一方、「Overload resolution ambiguity」は、複数の有効な候補が存在し、関数を呼び出す時点でどの候補を使用すべきか決定できない場合に発生します。

「Conflicting overloads」が表示された場合は、まず関数の呼び出し方ではなく、関数やコンストラクタなどの定義そのものが重複していないか確認するのが基本です。

関数名を変更して解決する

同じ引数構成で異なる処理を実行する必要がある場合は、関数名を分ける方法がもっとも分かりやすい解決方法です。

fun saveText(value: String) {
    println(value)
}

fun printText(value: String) {
    println(value)
}

処理の目的に合わせて名前を付けることで、競合を回避できるだけでなく、コードの意味も分かりやすくなります。

引数の型や数を変更して解決する

同じ名前の関数としてオーバーロードする必要がある場合は、引数の型や数に違いを持たせます。

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

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

fun show(value: String, count: Int) {
    repeat(count) {
        println(value)
    }
}

このように引数の型や数が異なれば、それぞれ別のオーバーロードとして区別できます。

Conflicting overloadsが表示されたときの確認ポイント

「Conflicting overloads」が表示された場合は、エラーになっている関数やコンストラクタについて、同じシグネチャの定義が存在していないか確認します。

  • 同じ名前で同じ引数型の関数を複数定義していないか
  • 引数名だけを変えた関数を作っていないか
  • 戻り値の型だけを変えてオーバーロードしていないか
  • デフォルト引数によって呼び出し方が重複していないか
  • 同じ引数構成のコンストラクタを複数定義していないか
  • 同じパッケージ内の別ファイルに同じトップレベル関数がないか
  • コピーやリファクタリングによって古い関数が残っていないか
  • 同じ拡張関数を重複して定義していないか
  • ジェネリクスの型パラメータ名だけを変えた同一構造の関数がないか

エラー箇所に表示されている関数だけを見るのではなく、プロジェクト内で同じ関数名を検索すると、重複した定義を見つけやすくなります。

まとめ

Kotlinの「Conflicting overloads」は、同じ名前の関数やコンストラクタなどが複数定義されており、コンパイラがそれらを別々の定義として区別できない場合に表示されるエラーです。

代表的な原因には、同じシグネチャの関数の重複、引数名だけを変更した定義、戻り値の型だけが異なる関数、同じ構成のコンストラクタ、トップレベル関数や拡張関数の重複などがあります。

基本的には、不要な重複定義を削除する、関数名を変更する、引数の型や数を変更するなどの方法で解決できます。

また、エラーが突然発生した場合は、コードのコピーや移動、リファクタリングによって同じ関数が複数残っていないか確認することも重要です。

「Conflicting overloads」が表示されたときは、呼び出し側ではなく定義側を中心に確認し、「コンパイラから見てそれぞれの関数を明確に区別できるか」を確認すると原因を見つけやすくなります。