関数から複数の値を返したいこと、ありますよね!?
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
関数のようなものはありません。
値はプロパティ first
、second
、third
で取り出せます。
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 class
、List
インターフェイスで分解宣言を使えるとお伝えしましたが、これらが特別扱いされているわけではありません。
自作のクラスでも使えるようにすることができます。
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
ではない普通のクラスにしました。
代わりに componentN
(N
は 1 始まりの整数)という operator
関数を追加しています。
これで data class
のときと同様に分解宣言が使えます。
このように分解宣言の N
番目の引数(?)にしたい値を componentN
という名前の operator
関数で返すようにすれば、自作のクラスでも分解宣言を使えるようになります。
data class
ではこの componentN
を自動的に実装してくれます。
そして Pair
クラスと Triple
クラスは実は data class
です(それぞれの API ドキュメントをご確認ください)。
/以上
-
JavaScript では同様の機能が分割代入(Destructuring assignment)と呼ばれています。 ↩
-
以前はあったとか… ↩
-
コード上で同じ型で表現しているというだけでなく、本質的に同等の型であるということ。例えば長さと重さをどちらも
Double
で表していたとしても、それらは本質的には異なる型です。 ↩