2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Scala] 値クラスについてまとめ

Posted at

なぜ書こうと思ったのか

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)

まとめ

一つずつ知識が増えていく。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?