不変、共変と反変について。Scalaプログラミング入門のP.218について
- Javaのクラスの配列は共変
- あるクラスの配列を引数として、宣言している場合、サブクラスの配列を引数として渡せる。String[] をObject[]を代入可能
- Javaのジェネリクスは共変ではなく、不変List<string> はList<Object>に代入できない。 Javaのクラスの配列は共変となっているが、型の安全性をコンパイル時に保証してくれない。
Scalaでは不変、共変、反変について指定することが可能
- 不変:[T]
- 共変:[+T]
- 反変:[-T]
不変
ScalaではArray[T]は不変です。foo(a:Array[String])メソッドに渡せるのはArray[String]のみです。ミュータブルの場合に、型引数を不変にすることが望ましい。
- class Holder[T](var data: T)
- def add(in: Holder[Int]) { in.data = in.data + 1 }
- val h = new Holder(0)
- add(h)
- println(h.data)
Holder[Number]はDoubleも保持できます。
- val nh = new Holder[Number](33.3d)
- def round(in: Holder[Number]) { in.data = in.data.intValue }
- println(round(nh))
- println(nh.data.getClass)
- val dh = new Holder(33.3d)
- round(dh) <--コンパイルエラー
共変
共変にする場合には、型引数の前に+をつける。
共変の型引数は読み取り専用のコンテナに便利
List[+T]と定義して、List[String]をList[Any]を引数としてメソッドに引数として渡すので、
Listのすべての要素はAnyである要件を満たしているため、Listは共変となります。Listの内容は変更できません。
サンプル
- class Getable[+T](val data: T)
- def get(in: Getable[Any]) { println("It's " + in.data) }
- val gs = new Getable("String")
- get(gs)
- def getNum(in: Getable[Number]) = in.data.intValue
- import java.lang.{ Double => JDouble }
- val gd = new Getable(new JDouble(33.3))
- println(getNum(gd))
子クラスの型に対して親クラスの型に代入することができる。
反変
Putable[String]を引数とするメソッドに対してPutable[AnyRef]を渡して呼び出せます。- class Putable[-T] {
- def put(in: T) { println("Putting " + in) }
- }
- def writeOnly(in: Putable[String]) { in.put("Hello") }
- val p = new Putable[AnyRef]
- writeOnly(p)
- //
- trait DS[-In, +Out] { def apply(i: In): Out }
- val t1 = new DS[Any, Int] { def apply(i: Any) = i.toString.toInt }
- def check(in: DS[String, Any]) = in("333")
- println(check(t1))
Putable[-T]で定義したときにPutable[AnyRef]をPutable[String]に代入
反変は共変の反対で親クラスの型に対して、子クラスの型に代入することができる。
変位ルール
- ミュータブルなコンテナは不変
- イミュータブルなコンテナは共変[+T]
- 変換処理の入力は反変[-T]に、出力は共変[+T]
ScalaのFunctionNトレイトは反変の引数と共変の戻り値を持っている。