1. 概要

引数が多い関数を呼び出すと、コードが読みづらくなったり、入力が誤って入れ替わったりするという間違いが発生する可能性があります。 関数へのオプションの入力の数を増やすと、関数のオーバーロードを介してデフォルトを提供することも面倒になる可能性があります。

Kotlinの2つの言語機能を使用することで、これらすべてを解決できます。

このチュートリアルでは、名前付き引数とデフォルト引数に対するKotlinの言語サポートについて説明します。

2. 位置引数

Kotlinの名前付き引数を調査する前に、位置引数の課題を思い出してみましょう。

私たちは常に位置引数を使用します。 位置引数は、宣言されているのと同じ順序で渡す必要があるメソッド引数です。 これらは、厳密なタイプの厳密な順序の入力の固定サイズ配列に少し似ています。

2.1. 位置引数の問題

複数の位置引数を持つ関数が呼び出された場合、呼び出し元のコードは、どのパラメーターがどれであるかを常に明確に説明しているわけではありません。

例を見てみましょう:

fun resizePane(newSize: Int, forceResize: Boolean, noAnimation: Boolean) {
    println("The parameters are newSize = $newSize, forceResize = $forceResize, noAnimation = $noAnimation")
}

位置引数を使用してこれを呼び出します。

resizePane(10, true, false)

IDEがサポートされていない場合、または resizePane 関数宣言を確認しない場合、resizePaneの呼び出しの引数が何を表すかを理解するのは困難です。 数値はサイズだと想像するかもしれませんが、2つのブール値は何かを意味する可能性があり、簡単に間違った順序になる可能性があります。

パラメータの数が増えるにつれて、または null 値の入力を許可しようとすると、問題はさらに悪化します。 Kotlinの名前付き引数は、この問題に対する適切な解決策を提供します。

3. Kotlinの名前付き引数

Kotlin関数を呼び出す場合、1つ以上の引数に名前を付けることができます。 引数に使用される名前は、関数宣言で指定されたパラメーター名と一致する必要があります。

3.1. 名前付き引数を使用した呼び出し

名前付き引数を使用して、resizePaneの呼び出しを書き直してみましょう。

resizePane(newSize = 10, forceResize = true, noAnimation = false)

このバージョンのコードは、より自己文書化されており、再確認が容易です。

3.2. 名前付き引数の順序

前に見たように、位置引数の順序は変更できません。 名前付き引数の順序はどうですか?

Kotlinの名前付き引数は任意の順序で渡すことができるので、2つのブール値を交換してみましょう。

resizePane(newSize = 11, noAnimation = false, forceResize = true)

すべての引数に名前が付けられているので、好きな順序でそれらを渡すことができます。

resizePane(forceResize = true, newSize = 12, noAnimation = false)

3.3. 名前付き引数と位置引数の混合

名前付き引数には読みやすさの利点がありますが、コードにテキストを追加することもできます。 簡潔にするために位置引数を使用できると便利ですが、役立つ場合は明確にするために名前付きパラメーターの使用を組み合わせてください。

幸い、1回の呼び出しで名前付き引数と位置引数を混在させることができます。 ただし、Kotlin 1.3では、すべての位置引数を名前付き引数の前に配置する必要があったことに注意してください。 Kotlin1.4はこの制限をもう課していません。

Kotlin 1.4では、順序が維持されていれば、名前付き引数と位置引数を混在させることができます

これを試してみましょう。 Kotlin 1.3では、位置引数にちなんで引数にのみ名前を付けることができます。

resizePane(20, true, noAnimation = false)

また、最近のバージョンでは、名前付き引数の途中で位置引数を使用できます。

resizePane(newSize = 20, true, noAnimation = false)

同様に、最後の引数のみを位置引数として渡すことができます。

resizePane(newSize = 30, forceResize = true, false)

一方、位置引数の途中で名前付き引数を使用できます。

resizePane(40, forceResize = true, false)

これらはそれぞれ同じ効果があります。

名前付きパラメーターの能力は、デフォルトと組み合わせるとはるかに大きくなり、関数定義のどこにあるかに関係なく、必要な入力のみを指定できるようになります。

4. デフォルトの引数

関数は、パラメーターの一部をオプションとして扱い、値が指定されていない場合はそれらのパラメーターのデフォルト値を想定したい場合があります。 Javaを含む多くの言語は、この機能をサポートしていません。 このような場合、値が指定されていない場合にデフォルト値をパラメーターに割り当てるコードを作成する必要があります。

さらに、関数のオーバーロードを使用して、メソッドのさまざまなバージョンを提供する場合があります。各バージョンでは、呼び出し元が1つ以上のオプションのパラメーターをスキップできます。

ただし、パラメータの数が増えると、メソッドのオーバーロードがすぐに制御不能になり、パラメータの意味を特定するのがさらに困難になる可能性があります。

Kotlinは、デフォルトの引数の言語サポートを提供します。 その結果、関数のオーバーロードを減らすことができます。

4.1. Kotlinのデフォルトの引数

デフォルトのパラメーター値は、関数宣言のパラメータータイプの後に=記号を使用して指定されます。

fun connect(url: String, connectTimeout: Int = 1000, enableRetry: Boolean = true) {
    println("The parameters are url = $url, connectTimeout = $connectTimeout, enableRetry = $enableRetry")
}

ここでは、connectTimeoutおよびenableRetryパラメーターのデフォルト値を指定しました。

次に、デフォルトの引数を使用して関数を呼び出す方法を見てみましょう。

4.2. デフォルトの引数を使用して関数を呼び出す

connectTimeoutおよびenableRetryパラメーターはデフォルト値で宣言されているため、関数呼び出しでそれらを除外できます。

connect("http://www.baeldung.com")

これは、スキップされた両方の引数にデフォルト値を使用します。

次に、enableRetry引数のみをスキップしましょう。

connect("http://www.baeldung.com", 5000)

これは、enableRetry引数のデフォルト値を使用します。

4.3. 真ん中の議論をスキップする

次に、真ん中の引数connectTimeoutだけをスキップしましょう。

connect("http://www.baeldung.com", false)

コンパイラエラーが発生します:

The boolean literal does not conform to the expected type Int

何が悪かったのか? 2番目の引数connectTimeoutをスキップし、代わりにBoolean引数enableRetryを渡しました。 ただし、コンパイラは2番目の引数としてInt値を想定しています。 デフォルト値の引数がスキップされると、後続のすべての引数は名前付き引数として渡される必要があります

connectTimeout 引数をスキップしたため、enableRetryを名前付き引数として渡す必要があります。

connect("http://www.baeldung.com", enableRetry = false)

これでエラーが修正されます。 これは、名前付きパラメーターがデフォルトの引数を補完する方法の良い例です。 名前付きパラメーターがないと、位置引数を使用してデフォルトの利点を得ることができません。

5. 関数とデフォルトの引数のオーバーライド

これまで、通常の関数のデフォルトの引数について見てきました。 ここで、オーバーライドされた関数のコンテキストでのデフォルトの引数を理解しましょう。

5.1. デフォルトのパラメータ値を継承する

オーバーライドは、基本クラス関数で宣言されたデフォルトのパラメーター値を使用します。

AbstractConnector という抽象クラスを宣言して、これを試してみましょう。

open class AbstractConnector { 
    open fun connect(url: String = "localhost") { 
        // function implementation 
    }
}

AbstractConnectorクラスのconnect関数には、デフォルト値のurlパラメーターがあります。

次に、このクラスを拡張して、connect関数をオーバーライドしましょう。

class RealConnector : AbstractConnector() {
    override fun connect(url: String) {
        println("The parameter is url = $url")
    }
}

ご想像のとおり、 url 引数の値を渡すことで、オーバーライドされたconnectメソッドを呼び出すことができます。

val realConnector = RealConnector()
realConnector.connect("www.baeldung.com")

ただし、オーバーライドされたconnect関数のurlパラメーターも、基本クラス関数で宣言されたデフォルト値を使用します。

したがって、url引数なしでオーバーライドを呼び出すことができます。

realConnector.connect()

これは、リスコフの置換原則の観点から理解できます。 オーバーライドされた関数はすべて、それらを宣言するスーパータイプから呼び出されると考える必要があります。 では、デフォルトを変更したい場合はどうでしょうか。

5.2. デフォルト値を上書きする

サブクラスRealConnectorurlパラメーターのデフォルト値を指定してみましょう。

class RealConnector : AbstractConnector() {
    override fun connect(url: String = "www.baeldung.com") { // Compiler Error 
        // function implementation
    }
}

これは許可されておらず、コンパイラエラーが発生します。 オーバーライドする関数は、パラメーターのデフォルト値を指定できません。 Kotlinでは、デフォルト値は基本クラス関数でのみ指定できる必要があります。

6. 結論

この記事では、名前付き引数とデフォルトの引数値に対するKotlinのサポートについて説明しました。 まず、位置引数が読みにくいことを確認しました。 次に、名前付き引数に対するKotlinのネイティブサポートと、これが多数の引数で読みやすさを向上させるのにどのように役立つかを調べました。

最後に、Kotlinのデフォルト引数のサポートにより、開発者はデフォルト値を実装するコードを記述できなくなり、関数の過度のオーバーロードを回避できることがわかりました。

いつものように、この記事のサンプルコードは、GitHubから入手できます。