第7章も相変わらず 型 について語るぞ!
今回の主役は、Javaプログラマーには馴染みがない タプル !
複数の戻り値
戻り値で複数の異なる型を返したくなったことはある?
Javaだと引数の型は1つと決まっているから、単純には実現できないよね。
だから、代替案としてこんな手を使ったりするよね。
- 例えば、新たなDTO等の 情報格納役 を定義し、そのフィールドに複数の型を用意する
- 引数に複数のオブジェクトを渡して、戻り値相当をそのオブジェクトに設定する
- インタフェースを分割する。つまりそれぞれのデータを返すメソッドを用意する
- 戻り値をコレクションにして、複数の型のオブジェクトを格納する(ほとんどやらないけど)
どれも面倒臭いんだよね。クラス名決めるのだって大変だし。。。
Javaの世界では諦めモードだけど、Scalaの世界では素晴らしい仕組みが備わっているんだ。
複数の型を返す仕組み、 タプル だ!
タプルの例
早速ソースを見てみよう。
object Tuple {
def getMaxValue(numbers: List[Int]) = {
val max = numbers.max
val index = numbers.indexOf(max)
(max, index)
}
def main(args: Array[String]){
val numbers = List(1, 2, 3, 4, 5, 10, 6)
val maxValue = getMaxValue(numbers)
printf("max=%s \n", maxValue._1)
printf("index=%s \n", maxValue._2)
}
}
実行
実行すると以下のようになるよ。
$ scalac Tuple.scala
$ scala Tuple
max=10
index=5
ソースの説明
Tuple.scalaは単純なソースだ。
引数にInt
のList
を受け取り、以下の戻り値を返すメソッドgetMaxValue
を定義する。
- 引数の
List
の 最大値 - 引数の
List
の 最大値のインデックス
そして、その戻り値を確認する。
この説明だけで、メソッドgetMaxValue
内でどれがタプルかわかるだろ?
(max, index)
がタプルだ。
val max = numbers.max
val index = numbers.indexOf(max)
List
の最大値と最大値のインデックスはここで取得しているんだ。
val maxValue = getMaxValue(numbers)
printf("max=%s \n", maxValue._1)
printf("index=%s \n", maxValue._2)
タプルを変数maxValue
に格納して、_1
_2
でそれぞれの要素にアクセスしている。
格納した順番通りなので
-
_1
が最大値 -
_2
が最大値のインデックス
となる。
タプルとは
タプルとは、 複数の異なる型 を格納できるコンテナオブジェクトだ。
つまり 情報格納役 になる。
もちろんタプルにも型が存在している。
scala.TupleN
として、1~22のNに対してTupleNクラスが定義されている(ようだ)。
ということは、タプルには22個まで要素を格納できる。
また、それぞれの要素にアクセスするには_N
とする。
タプルの限界
22個なんて使うことはないと思いつつ、22個を超過したらどうなるか試してみる。
Tuple22
22個は最大値なので問題ないはず。
object Tuple22 {
def get() = {
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22)
}
def main(args: Array[String]){
val tuple = get()
printf("element_1=%s \n", tuple._1)
printf("element_22=%s \n", tuple._22)
}
}
22個の要素を持つタプルを用意して、1要素目と22要素目を取得する。
実行
$ scalac Tuple22.scala
$ scala Tuple22
element_1=1
element_22=22
これは期待通りの動きとなった。
Tuple23
23個の要素を持つタプルは、型が存在しないのでscalac
でエラーになるはず。
object Tuple23 {
def get() = {
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23)
}
def main(args: Array[String]){
val tuple = get()
printf("element_1=%s \n", tuple._1)
printf("element_23=%s \n", tuple._23)
}
}
実行
scalac
してみると
$ scalac Tuple23.scala
Tuple23.scala:4: error: object Tuple23 is not a member of package scala
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23)
^
one error found
Tuple23
なんていないぜって言っているから、予想通りだ。
23個以上は使うなよ!
Function22
Function22
というのもあるんだ。
引数の数に応じて、Function1
~Function22
まで存在している。
つまり23個以上は使えないってこと?
object Func22 {
def main(args: Array[String]) {
val func22 : (Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int) => Int = null;
}
}
実行
scalac
してみるよ!
scalac Func22.scala
コンパイルエラーはでないね。
Function23
次はFunction23
を見てみよう。
object Func23 {
def main(args: Array[String]) {
val func23 : (Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int) => Int = null;
}
}
実行
scalac
してみるよ!
$ scalac Func23.scala
Func23.scala:3: error: type Function23 is not a member of package scala
val func23 : (Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int) => Int = null;
^
one error found
今度はコンパイルエラーになったね。Function23
なんて型はないぜ!って言っている。
関数の引数は、やっぱり22個までということですね。
情報格納役オブジェクトで試す
Int
の場合で試してみたので、今度は情報格納役オブジェクト
所謂JavaBean的なオブジェクトで試してみよう!
振る舞いも入れてみてるからな。
object CaseClassTuple {
def get() = {
val name = new Name("清美", "椿山")
(Human(name, 15), name)
}
def main(args: Array[String]){
val tuple = get()
printf("fullname_1=%s \n", tuple._1.name.fullname)
printf("fullname_2=%s \n", tuple._2.fullname)
}
}
class Name(first: String, last: String) {
def fullname() = first + " " + last
}
case class Human(name: Name, age: Int)
実行
実行してみる。
$ scalac CaseClassTuple.scala
$ scala CaseClassTuple
fullname_1=清美 椿山
fullname_2=清美 椿山
これもウマくいった。
ほぼほぼどんなオブジェクトでもいけそうな予感。
タプルもイミュータブル
もちろんというか、タプルもイミュータブルなので要素の入れ替えができないよ。
ちょっとチャレンジしてみました。
object TupleImmutable {
def get(x1: Int, x2: Int) = {
(x1, x2)
}
def main(args: Array[String]){
val tuple = get(1, 2)
tuple._1 = 3
}
}
実行
scalac
してみる。
$ scalac TupleImmutable.scala
TupleImmutable.scala:9: error: reassignment to val
tuple._1 = 3
^
one error found
__イミュータブルだぜ!アサインできないぞ!__と言っているね!
タプルのメリット
型の増大を防ぐことができるのがメリットだと思うよ。
特に、サービス層で使える気がしている。
異なる型のデータ取得のためだけに、サービスのインタフェースを複数切っていたところを、
Facadeにまとめてしまい、タプルでデータを返すってコトができると思う。
他にも使い道ありそうだね。
まとめ
今回はタプルについて語ってみたけどどうかな?
自分が疑問に思っていたところも試すことが出来たから、良かったぞ。
今回も
体で感じてくれたかな?