Kotlinのvarargについて

採用はこちら

Kotlinの vararg(可変長引数)は、表面的にはシンプルですが、配列との関係・スプレッド演算子(*)・オーバーロード解決・パフォーマンス特性など、理解が浅いと設計ミスにつながりやすい機能です。

ここでは「単なる使い方」ではなく、なぜそう動くのか/どこで使うべきかまで含めて解説します。

目次

varargとは何か(基本)

vararg は「引数の個数を可変にできる仕組み」です。

fun printAll(vararg messages: String) {
    for (msg in messages) {
        println(msg)
    }
}

呼び出し側

printAll("Hello")
printAll("Hello", "World")
printAll("Kotlin", "is", "fun")

重要な前提

  • vararg で受け取った値は 関数内ではまとめて扱われる
  • 見た目は「複数引数」だが、内部的には配列

varargの正体:実体は「配列」

Kotlinでは、vararg パラメータは 配列として扱われます

fun sample(vararg nums: Int) {
    println(nums.size)
}

この nums は、関数内では IntArray です。

型ごとの内部表現

宣言関数内での型
vararg x: IntIntArray
vararg x: DoubleDoubleArray
vararg x: BooleanBooleanArray
vararg x: StringArray<String>

この点は パフォーマンスやJava連携 に直結します。

varargは1つだけ & 位置制約

varargは1つしか定義できない

// コンパイルエラー
fun bad(vararg a: Int, vararg b: String) {}

varargが最後でない場合

vararg は原則として 最後の引数 に置きます。

もし最後でない場合、後続引数は名前付き引数で指定する必要があります

fun example(vararg nums: Int, label: String) {}

example(1, 2, 3, label = "test") // OK

スプレッド演算子(*)は必須知識

配列を vararg に渡す場合

fun sum(vararg nums: Int): Int = nums.sum()

val array = intArrayOf(1, 2, 3)

// ❌ エラー
// sum(array)

// ✅ 正しい
sum(*array)

* の意味

  • array → 「配列1つを渡す」
  • *array → 「配列を展開して複数引数として渡す」

補足(重要)

* による展開は、状況によっては配列コピーが発生します。

sum(0, *array, 99)

このような場合、新しい配列を生成して結合する必要があるため、高頻度処理ではコストになる可能性があります

vararg + 通常引数(よくある実務パターン)

fun log(level: String, vararg messages: String) {
    println("[$level]")
    messages.forEach { println(it) }
}
log("INFO", "start", "processing", "end")

実務での代表例

  • ログ出力
  • SQLのIN句構築
  • UIイベントの引数束ね
  • DSL(後述)

ジェネリクスとvararg

fun <T> asList(vararg items: T): List<T> =
    items.toList()
val a = asList(1, 2, 3)
val b = asList("A", "B")

混在型の場合

val mixed = asList(1, "A", true)

この場合、共通の上位型が推論され、典型的には List<Any?> になります。

Javaとの相互運用

Kotlin → Java

fun printAll(vararg values: String) {}

Java側

printAll("a", "b", "c");

Java → Kotlin

void log(String... messages)

Kotlin側

log("a", "b")
log(*arrayOf("a", "b"))

Javaの ... と Kotlin の vararg概念的に同等ですが、
Kotlin側では * が必要になる点が違いです。

varargとオーバーロードの注意点(誤解されやすい)

fun test(a: Int) {}
fun test(vararg a: Int) {}

これは コンパイルエラーではありません

  • test(1)固定長引数の方が優先される
  • test(1, 2) → vararg が呼ばれる

ただし

  • APIとしては 読み手に意図が伝わりにくい
  • デフォルト引数などと組み合わさると、可読性が急激に落ちる

実務では避ける設計が無難

パフォーマンスの観点(重要)

vararg は呼び出し時に 配列を生成します。

repeat(1_000_000) {
    foo(1, 2, 3)
}
  • 毎回配列が生成される
  • spread(*)を使うとコピーも発生し得る

対策

  • 高頻度処理では List / Array を直接受け取る
  • APIの「使いやすさ」と「性能」を意識して選択

DSL・API設計での使いどころ

良い例(DSL)

fun html(vararg children: Tag) {}
html(
    h1("Title"),
    p("Hello"),
    p("World")
)

悪い例(乱用)

fun process(vararg params: Any?)
  • 型安全性が崩壊
  • 呼び出し側も実装側も意図が不明確

よくある勘違いまとめ

勘違い実際
varargはList実体は配列
配列はそのまま渡せる* が必要
何個でも定義できる1つだけ
パフォーマンスに影響しない配列生成コストあり

まとめ

  • varargAPIの使いやすさを高めるための機能
  • 内部では 配列が生成されることを理解する
  • *(スプレッド演算子)はコピーが発生し得る
  • 高頻度処理・大量データでは慎重に使う
  • DSL・補助的API・ログ用途に特に向いている

以上、Kotlinのvarargについてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次