ペンギン村 Tech Blog

技術をこよなく愛するエンジニア集団が在住するペンギン村から、世界へ役立つ(かもしれない)技術情報を発信する技術系ブログです。某アラレちゃんが済む村とは一切関係ありません。んちゃ!

Kotlinで可変長引数を利用する時は、同一の型の引数を一緒に使用するのを避ける

こんにちは、tobi462(過去記事一覧)です。

今日は、Kotlinで可変長引数を使用した時にハマったことがあったので、アンチパターンの共有です。

可変長引数(vararg)

可変長引数の概念はJava5からあるもので、Kotlinでも似たように配列として引数を受け取る仕様になっていますが、構文が少し違います。

Kotlinではvarargという修飾子を用いて、可変長引数を宣言します。

// 宣言
fun printAll(vararg values: String) {
    println(values::class.java) // => class [Ljava.lang.String;
    values.forEach {
        println(it)
    }
}

// 使用
printAll("Apple", "Orange", "Banana")

配列自体を引数として渡す場合には、先頭に*をつけます。

val fruits = arrayOf("Apple", "Orange", "Banana")
printAll(*fruits) // *を頭につけないとコンパイルエラー

同一の型の引数を一緒に使う

さて、ここでクイズです。

以下のコードは先程の関数にデフォルト引数を追加したものですが、何が出力されるでしょうか?

fun printAll(vararg values: String, postfix: String = ".") {
    values.forEach {
        println(it + postfix)
    }
}

val fruits = arrayOf("Apple", "Orange", "Banana")
printAll(*fruits, "!") // クイズ:何が出力される?

おそらくここまでの流れから答えの察しはつくのではないかと思います。

以下のように出力されます。

Apple.
Orange.
Banana.
!.

つまり以下のコードは一見すると、可変長引数としてfruitsを渡し、第二引数として”!”postfixとして渡しているように見えますが、実際にはどちらも可変長引数として扱われているのが分かります。

printAll(*fruits, "!") // どちらも可変長引数として渡される

ちなみに第二引数にデフォルト値が使用されていない場合は、名前付き引数で明示しないとコンパイルエラーになるため、この問題は起こりません。

printAll(*fruits, postfix = "!") // `postfix = `がなければコンパイルエラー

教訓としてのアンチパターン

今回得られた教訓としては、可変長引数を利用する時は、同一の型の引数を一緒に使用するのを避けるべきであるということです。

デフォルト引数を利用しなければ問題ないと感じるかもしれませんが、それでも引数の境界が分かりづらくなり、APIとしては利用者に誤解を与えやすいため避けるべきかと思います。

最後に

といった形で、今日はKotlinのアンチパターンについての記事でした。

Kotlinはよくデザインされている言語だと思いますが、こういったハマリポイントもあるのだと分かったのは良い収穫だったと思います。

ではまた次回。