LoginSignup
50
43

More than 1 year has passed since last update.

【Kotlin】複数の値を返したい!

Last updated at Posted at 2019-02-21

Kotlin 1.7.20

関数から複数の値を返したいこと、ありますよね!?

2つの値を返す(Pair クラス)

2つの値を1つにまとめたいときは Pair クラスを使いましょう。

// 名前とバージョンを返す関数
fun getNameAndVersion(): Pair<String, Double> {
    return Pair("Kotlin", 1.7)
}

fun main() {
    val nameAndVersion = getNameAndVersion()
    val name = nameAndVersion.first // -> "Kotlin"
    val version = nameAndVersion.second // -> 1.7
}

1つ目の値は first プロパティ、2つ目の値は second プロパティで、それぞれ取り出せます。

Pair インスタンスを簡単に作る(to 関数)

to 関数を使うと簡単に Pair インスタンスを作れます。

fun getNameAndVersion(): Pair<String, Double> {
    return "Kotlin" to 1.7
}

to 関数は2項演算子のように2つの値の間に置けます。(中置記法(infix notation) と言います。)

返値を2つの値として受け取る(分解宣言)

2つの値を Pair インスタンスとしてではなく2つの値として受け取ることもできます。(分解宣言(Destructuring Declarations) と言います。1

fun main() {
    val (name, version) = getNameAndVersion()
}

3つの値を返す(Triple クラス)

3つの値をまとめたいときは Triple クラスを使いましょう。

val triple = Triple("Kotlin", 1.7, 2022)

Pair クラスでの to 関数のようなものはありません。

値はプロパティ firstsecondthird で取り出せます。

val name = triple.first // -> "Kotlin"
val version = triple.second // -> 1.7
val release = triple.third // -> 2022

分解宣言も使えます。

val (name, version, release) = triple

4つ以上の値を返す(data class

4つ以上の値をまとめる型は標準ライブラリーには用意されていません。2
自作する必要がありますが、data class を使えば簡単です。

data class Lang(
    val name: String,
    val version: Double,
    val release: Int,
    val isGood: Boolean,
)

val kotlin = Lang("Kotlin", 1.7, 2022, true)

分解宣言も使えます。

val (name, version, release, isGood) = kotlin

🚫アンチパターン:配列やリストを使う

Array<Any?> 型や List<Any?> 型を使えば
値がいくつでもどんな型でも返すことができますが、
絶対にやってはいけません。

理由は次の通りです。

  • 各値が型安全でない
  • 値の数が不足していてもコンパイルエラーにならない

Array<T>List<T> を使うべきなのは、
要素の型が本質的に同じ3で、
要素数が不定もしくは多数の場合だけです。

⚠️注意:既存の型を使えないか検討する

上述の方法よりも先に、既存の型を使えないか検討しましょう。

例えば、整数を数え上げる範囲を表すなら、
Pair<Int, Int> 型よりも IntRange クラスを使うべきです。

⚠️注意:独自の型を定義することを検討する

既存の型に適切なものがなく、
Pair クラスや Triple クラスが使える場合でも、
独自の型を定義することを検討しましょう。

独自の型を定義すれば、
型名やプロパティを見ただけでそれが何を表しているか明確になりますし、
偶然型が同じになった別の Pair などと混同してしまうこともなくなります。

次のような場合には独自の型を定義すべきです。

  • 多くの箇所で使われる
  • 値の取り違えをしやすい
    • 型が同じで順番に意味がないなど。例えば幅と高さ

おまけ:分解宣言についてもう少し

ラムダ式の引数にも使える

ラムダ式の引数部分でも分解宣言を使えます。

listOf(
    0 to 'A',
    1 to 'B',
    2 to 'C',
).forEach { (number, alphabet) ->
    println("$number: $alphabet")
}

List インターフェイスの第5要素までにも使える

List インターフェイスの第1〜第5要素には分解宣言を使えます。

val list = listOf('A', 'B', 'C', 'D', 'E', 'F')
val (a, b, c, d, e) = list

分解宣言の入れ子はできない

分解宣言を入れ子にすることはできないようです。

val numbers = (1 to 1L) to (1.0 to 1.0F)
val ((i, l), (d, f)) = numbers // コンパイルエラー!

自作クラスでも使えるようにできる

Pair クラス、Triple クラス、data classList インターフェイスで分解宣言を使えるとお伝えしましたが、これらが特別扱いされているわけではありません。
自作のクラスでも使えるようにすることができます。

class Lang(
    val name: String,
    val version: Double,
    val release: Int,
    val isGood: Boolean,
) {
    operator fun component1() = name
    operator fun component2() = version
    operator fun component3() = release
    operator fun component4() = isGood
}

fun main() {
    val kotlin = Lang("Kotlin", 1.7, 2022, true)
    val (name, version, release, isGood) = kotlin
}

前出の Lang クラスを data class ではない普通のクラスにしました。
代わりに componentNN は 1 始まりの整数)という operator 関数を追加しています。
これで data class のときと同様に分解宣言が使えます。

このように分解宣言の N 番目の引数(?)にしたい値を componentN という名前の operator 関数で返すようにすれば、自作のクラスでも分解宣言を使えるようになります。

data class ではこの componentN を自動的に実装してくれます。
そして Pair クラスと Triple クラスは実は data class です(それぞれの API ドキュメントをご確認ください)。

/以上

  1. JavaScript では同様の機能が分割代入(Destructuring assignment)と呼ばれています。

  2. 以前はあったとか…

  3. コード上で同じ型で表現しているというだけでなく、本質的に同等の型であるということ。例えば長さと重さをどちらも Double で表していたとしても、それらは本質的には異なる型です。

50
43
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
50
43