なぜ書こうと思ったのか
AnyValに関して知識なかったので、公式ドキュメントのことを自分なりにまとめてみた。
てか、ほぼ公式ドキュメントと同じ。
ソース
公式ドキュメント「 値クラスと汎用トレイト」
詳しく書かれているのでありがたい。
値クラスの定義
AnyValを継承することで値クラスを定義できる。
class UserId(val value: string) extends AnyVal
値クラスにすることで、実行時のオブジェクトのメモリ割り当てを回避できる。
つまり、ヒープにメモリを割り当てをすることをしなくても良くなるので、実行時のオーバーヘッドを無くせる。
上記例では、コンパイル時にはUserIdという型だが、実行時にはString型になる。
制約
値クラスには制約が多くあるので、それを見ていく。
一つのパラメータしか受け取れない
// これはできない
class UserId(val value1: String, val value2: String) extends AnyVal
defだけ定義できる
class UserId(val value: String) extends AnyVal {
def print(): Unit = println(value)
}
汎用トレイトのみ継承できる
他のクラスなどは継承できない。
trait Printable {
def print(): Unit
}
class UserId(val value: String) extends AnyVal with Printable {
override def print(): Unit = println(value)
}
値クラスの用例
拡張メソッド
implicit classで拡張メソッドを定義する時に、値をクラス定義すると実行時にオーバーヘッドのない拡張メソッドを作成できる。
implicit class RichInt(val value: Int) extends AnyVal {
def toHexString: String = java.lang.Integer.toHexString(value)
}
3.toHexString
上記の例で言うと、toHexString
メソッドを利用するために、RichIntをインスタンス化する必要がなくなると言うこと。なので、実行時のオーバーヘッドをなくせる。
正当性
実行時には、オーバーヘッドを無くしながら、実装時には型安全を得ることができる。
class Meter(val value: Double) extends AnyVal{
def +(x: Meter): Meter = Meter(value + x.value)
}
val x = new Meter(3.0)
val y = new Meter(4.0)
val z = x + y
実装時(コンパイル時)には、Meter型は守られるので、安全に実装できる。
実行にはMeter型は消えるので、オーバーヘッドがなくなる。
メモリ割り当てが必要になる場合
(1) 他の型として利用する場合
以下のように、親型(Distance
)で関数のパラメータを受け取る場合は、Meter
型はインスタンス化される。
trait Distance
class Meter(val value: Double) extends AnyVal with Distance
def add(x: Distance, y: Distance): Distance = ???
add(new Meter(3.0), new Meter(4.0))
以下のようならインスタンス化されない
def add(x: Meter, y: Meter): Meter = ???
add(new Meter(3.0), new Meter(4.0))
ちなみに、型パラメータの場合もメモリ割り当てが必要になる。
def add[A](x: A, y: A): A = ???
add(new Meter(3.0), new Meter(4.0))
(2)配列の中に組み込まれる場合
以下の場合、内側のDouble型を格納するのではない。
Meter実体を格納する。
val m = new Meter(3.0)
val array = Array[Meter](m)
(3) パターンマッチで型でマッチさせる場合
型検査する場合は、インスタンス実体が必要になる。
val p = new Meter(3.0)
p match {
case Meter(3.0) => println("3です。")
case Meter(_) => println("3以外です。")
}
以下のように値クラスのコンパニオンオブジェクトで、applyをオーバーロードしてもMeterはインスタンス化されない!??
case class Meter(val value: Double) extends AnyVal
object Meter {
def apply(value: Double): Meter = new Meter(value + 1.0)
}
val m = Meter(3.0)
まとめ
一つずつ知識が増えていく。