[Kotlin]自作関数にデフォルト引数や可変長引数を設定する方法

今回は関数のパラメータ、引数の受け取り方と、呼び出し時の渡し方について見ていこうと思います。

パラメータの記述の仕方、あるいは関数の呼び出し方によって、引数の扱い方が大きく広がるというのが、初心者のうちは煩わしくて難しいところですが、慣れてくると便利な道具となってくれます。

通常のパラメータ

まずは前回ご紹介した通常のパラメータ設定です。

fun main() {
    implore("安西先生",3)
}

fun implore(name: String,count: Int){
    print(name+".".repeat(count))
    println("バスケがしたいです")
}

//安西先生...バスケがしたいです

implore関数は

  1. nameというString型とcountというInt型を引数に取り、
  2. 受け取ったnameに、countの数だけ「.」を付け加えたものを改行無しで標準出力し、
  3. 最後に"バスケがしたいです"という文字列を出力しています。

(ちなみにどうでもいいですが、imploreの和訳は「懇願する」です)

前回もお話したようにこのパラメータ内では仮引数の型指定は省略できません。一度復習してみましょう。

「この関数定義で省略されているのは何でしょうか?」

可視性修飾子(public)と戻り値の型(Unit)が出てきた方は正解です。次に進みましょう。

この時点で「何それ?」という場合は、関数定義の基本を知っておいた方が後々スムーズに進むことができるので、よければこちらもご覧ください。

https://pouhon.net/function-define/1256/

デフォルト引数を設定する

さてこの関数ですが、呼び出し時に引数が渡されなければどうなるでしょう?

fun main() {
    implore()
}
エラー:(2, 13) Kotlin: No value passed for parameter 'name'
エラー:(2, 13) Kotlin: No value passed for parameter 'count'

エラー: パラメータのための値が渡されていない

Kotlinの言い分はもっともです。ただ「バスケがしたいです」というセリフは「安西先生」に対して言われるものと相場が決まっています。少なくともスラムダンク世代ではそういう認識です。

もう少し一般的な言い方をすれば、他の値もあり得るけれど、当たり前の値を渡す場合は引数の記述を省略したいという場合も多いと思います。そういった場合はどうすればいいのか?

そこで使えるのがデフォルト引数です。

文字通り関数のデフォルトの引数を設定しておくことで、引数無しで呼び出されたときはデフォルトの値が適用され、エラーにはなりません。

それではデフォルト引数を使って関数を定義し、引数無しで呼び出してみましょう。

fun main() {
    implore()
    implore("田中くんと",6)
}

fun implore(name: String = "安西先生", count: Int = 3){
    print(name+".".repeat(count))
    println("バスケがしたいです")
}

//安西先生...バスケがしたいです
//田中くんと......バスケがしたいです

このように「パラメータの型指定=デフォルトの引数」と記述しておくことで、引数無しで関数が呼び出された場合、デフォルトの値が自動的に呼び出されます。

オーバーロード – 関数の多重定義

さて先ほどの問いに対して「引数無しの関数を別に定義する」と考えた方もいるかも知れません。例えばこのような形で。

fun main() {
    implore()
    implore("田中くんと",6)
}

fun implore(name: String,count: Int){
    print(name+".".repeat(count))
    println("バスケがしたいです")
}

fun implore(){
    println("安西先生...バスケがしたいです")
}

「そんな同じ名前の関数なんか定義できるわけねぇじゃねーか」と思うのが普通の感覚ですが、実はこれは正しいコードです。実行して確かめてみてください。

なぜ同じ関数名なのにエラーにならないのか? この場合一言で言えば、それはパラメータが違うからです。

パラメータの設定が異なる場合、コンパイラはどちらの関数が呼ばれたのかを渡された引数によって判断し、正しい方の関数を呼び出してくれます。

この性質を利用して処理の異なる2つ以上の同名関数を定義することをオーバーロード(関数の多重定義)と言います。

パラメータが違うというのが条件なので、以下のような多重定義は、例え処理が違っていてもエラーになります。

fun implore(name: String,count: Int){
    print(name+".".repeat(count))
    println("バスケがしたいです")
}

fun implore(she: String,day: Int){
    println("${she}さん、${day}日空いてませんか?")
}

懇願しても無理なもんは無理です。

名前付き引数

続いて関数呼び出し側のオプションに移ります。渡す引数の順番についてです。

関数が受け取る引数の順番はパラメータによって決められています。例えば下のコードでは、引数の順番が違うと実行結果も変化します。

fun main() {
    println(math(5,9,3))
    println(math(3,5,9))
}

fun math(x: Int,y: Int,z: Int): Int{
    return x*y/z
}
//15
//1

単純に2つの数値を掛けてから最後の引数で割るという処理ですが、この関数に引数を渡す順番は、意図した答えを得ようとすると固定でなくてはなりません。計算結果が変わってしまうからです。

今回の例では全ての引数がInt型なのでまだエラーにはなりませんが、型が違う引数を受け取るような関数の場合、順番が違えばエラーになってしまいます。

そこで、呼び出し時に引数に名前を付けることで、順番の違いによるエラーを回避しようというのが名前付き引数です。

fun main() {
    println(math(5,9,3))
    println(math(z=3,x=5,y=9))   //3はz,5はxと名前を付けて渡す
}
//15
//15

関数定義のパラメータと同じ名前を呼び出し時の引数に指定すると、その引数は関数に渡った時点でxはxとして正しく並び替えられ、結果として正しい計算結果が得られます。

可変長引数

最後に可変長引数をご紹介しましょう。

可変長引数はこれも文字通りですが、関数の呼び出し時に引数の長さ(個数)を自由に変化させることが出来る関数パラメータの定義です。

どういうことなのか。早速見ていきましょう。

fun main() {
    println(plus("合計は",5,9,3))
    println(plus("足し合わせると",4,29,109,8,2))
}

fun plus(str: String,vararg x: Int): String{   
    //↑引数にString型1つと、Int型の可変長引数を取る関数定義
    var sum = 0
    for(i in x){
        sum += i
    }
    val ans = sum.toString()
    return "$str...${ans}です"
}

//合計は...17です
//足し合わせると...152です

受け取った複数の整数を全て足した答えを出力する関数です。

関数定義の中で仮引数名の前にvarargと記述すると、その関数は可変長引数を取るということになります。

呼び出し側の引数の個数が変化しても、正しく計算できているのが分かると思います。

なぜこんなことが可能なのか? 答えは「配列」にあります。

可変長引数のカラクリ

まず関数呼び出しですが、上の呼び出しの時点で引数として渡されているのは、あくまで文字列1つと整数3つ、計4つのバラバラな値です。

可変長引数を取る関数はこの場合、受け取った引数を内部でString型1つと5,9,3の数字が格納された1つの配列に変換します。

この時点で関数が扱うことになる引数は、String型1つ(この場合str)と、Int型が入った配列1つ(この場合x)の、計2つになります。

ここから先はこの1つのString型と1つの配列を処理すればいいことなので、配列の中身の項目が何項目あろうと問題ではありません。これが可変長引数の中身です。

可変長引数は最後に受け取る

個数が何個であっても構わないという柔軟な可変長引数ですが、それだけに制約もあります。それは可変長引数は関数のパラメータの最後でなくてはならないということです。

いくら型が違うからと言って、最初のString型まで可変長にしてしまうとエラーが起こります。

fun main() {
    println(plus("合計は",5,9,3))
}

fun plus(vararg str: String,vararg x: Int): String{
    var sum = 0
    for(i in x){
        sum+=i
    }
    val ans = sum.toString()
    return "$str...${ans}です"
}
//エラー:(2, 24) Kotlin: The integer literal does not conform to the expected type String
//エラー:(5, 10) Kotlin: Multiple vararg-parameters are prohibited

エラー: 整数の値は、予期されたString型に適合していない

エラー: 可変長引数は禁止されている

さらに最初の仮引数だけを可変長にしたとしてもエラーになります。可変長なだけに終わりが判別できないからです。

2つ以上の配列を扱う関数であれば、素直にパラメータの引数を配列にするべきです。その場合呼び出し側でバラバラの値を渡すことはできませんが、配列を複数渡すことはできます。

おさらい

今回は関数パラメータと呼び出し側による、引数のコントロールについてお話しました。

ポイントとしては、

  1. デフォルト引数の意味と設定の方法
  2. オーバーロードとは?
  3. 名前付き引数の利点と設定方法
  4. 可変長引数の使い方と、使うための条件

こんな感じでしょうか。

次回はラムダ式(無名関数)について。

タイトルとURLをコピーしました