不変、共変と反変について。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))つまり、Getable[+T]で定義したときGetable[String]をGetable[Any]に代入することができる。
子クラスの型に対して親クラスの型に代入することができる。
反変
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トレイトは反変の引数と共変の戻り値を持っている。