[Scala In Depth] Scala Case Classes
這篇文章,基本上是參考這篇Scala Case Classes In Depth來寫的,閱讀吸收後以我的例子與描述方式以中文寫下。
大家對Case Class基本的認識
- 定義一個簡單的類型(class)並在宣告的時候直接定義好有哪些參數。
case class User(region: String, id: String, name: String)
- 可以簡單的用來建立某個類型(class)的物件,因為case class已經實作了apply,所以可省略
new
這個宣告方式,來建立新物件。
val joe = User("TW","123","Joe")
joe: User = User(TW,123,Joe)
- 每個參數的宣告都被加上了
val
的前綴(prefix),也就是這個class基本上每個property都是immutable(不能被修改的)。
val region = joe.region
// 下面這行在compile會錯誤
joe.region = "US"
<console>:23: error: reassignment to val
- 當然有一些基本的implementation,像是
hashCode
,euqals
和toString
…等,而在scala中,==
基本上就是執行equal
,所以在case class用==
去比較的時候,是用整個結構會去比的(而非比reference)。
scala> val joe2 = User("TW","123","Joe")
joe2: User = User(TW,123,Joe)
scala> joe == joe2
res: Boolean = true
scala> val joe3 = User("TW","124","Joe")
joe3: User = User(TW,124,Joe)
scala> joe == joe3
res54: Boolean = false
- 要複製的時候,可以使用
copy
,若有要修改的屬性,再copy當中宣告即可
scala> joe.copy(region="US")
res: User = User(US,123,Joe)
- 有實作
unapply
,所以在pattern matching可以直接使用。
scala> val User(region,id,name) = joe
region: String = TW
id: String = 123
name: String = Joe
scala> joe match {
| case User(_,id,_) => println(s"id:[$id]")
| }
id:[123]
- 如果在宣告case class的時候,並沒有要給任何的參數,就像是定義ADT(Algebraic Data Type)的時候,我們有可能會宣告到一個沒有任何參數的class,這時候可以用
case object
來宣告。
sealed trait Life[+T]
case class Alive[T](value: T) extends Life[T]
case object Dead extends Life[Nothing]
scala> def check(life:Life[User]) = life match {
| case Alive(user) => println(user.name)
| case Dead => println("dead")
| }
check: (life: Life[User])Unit
scala> check(Alive(joe))
Joe
scala> check(Dead)
dead
一些case class進階的認識
- 如果你需要建立一個function,而這個function要傳入所有產生這個class的參數,最後要產生這個class的object的話,你可以使用
apply
// 這裡的 _ 是因為要把apply這個function指派給變數來用,如果不加上 _ 的話,會被當成是要執行這個function而又因為後面沒有帶所需要的參數造成compile錯誤。
scala> val userCreator = User.apply _
userCreator: (String, String, String) => User = <function3>
scala> val uerica = userCreator("TW","66","Uerica")
uerica: User = User(TW,66,Uerica)
curried
,如果你要把上面這個用法,拆成是curry的話,可以使用curried
的方式,把這些參數給拆開來,方便組合使用。
scala> val curriedUser = User.curried
curriedUser: String => (String => (String => User)) = <function1>
// 上面其實就變成了一種這樣 (region: String)(id: String)(name: String) : User 的function
scala> val twUserCreator = curriedUser("TW")
twUserCreator: String => (String => User) = <function1>
scala> val samael = twUserCreator("111")("Samael")
samael: User = User(TW,111,Samael)
tupled
,前面提到的apply
是建立一個function並傳入對應class properties數量的參數,而若想要傳入的是一個tuple
,就可以使用tupled
。
scala> User.tupled
res67: ((String, String, String)) => User = <function1>
//上面這個與之前的apply不同,多了一組刮號,因為這傳入的是tuple
scala> val tupledJoe = ("TW","123","Joe")
tupledJoe: (String, String, String) = (TW,123,Joe)
scala> User.tupled(tupledJoe)
res: User = User(TW,123,Joe)
unapply
,當我們需要一個function,是傳入某個類型的物件,然後希望回傳的是Option[TupleN[A1, A2, ..., AN]]
,將N個參數分別包成tuple,然後用Option包起來(這邊用Option有可能是因為要handle這物件可能不存在的情況),這種時候就能直接使用unapply
。
scala> val toOptionOfTuple = User.unapply _
toOptionOfTuple: User => Option[(String, String, String)] = <function1>
scala> toOptionOfTuple(joe)
res: Option[(String, String, String)] = Some((TW,123,Joe))
當使用curried
的形式來建立一個case class時…
這篇文章提到了一點,就是我們可以用curried
的形式來建立一個case class,就像是下面的例子。
scala> case class Person(fingerPrint: Long)(name: String, dress: String)
defined class Person
scala> val j = Person(123123l)("Joe","Suit")
至於什麼時候要這樣用?以及該怎麼用?用了會發生什麼事?,我們分別來說明。
什麼時候要這樣用?
如果真的有一種情況,就是在你的設計需求上,希望一個物件的比對是只依照某個部份的property,例如我的例子是只要指紋是一樣的,就是同一個人,不論他的名字、裝扮如何。
scala> val j = Person(123123l)("Joe","Suit")
scala> val q = Person(123123l)("Andy","Casual")
scala> j==q
res: Boolean = true
該怎麼用?用了會發生什麼事?
curried
的部份,並沒有實作前面提到case class的那些特性,也就是說,下面這幾點都會有問題:
- 沒有實作
val
的前綴,所以不能直接存取這些properties
scala> j.name
<console>:24: error: value name is not a member of Person
j.name
^
所以我們必須自己加上val
的宣告
scala> case class Person(fingerPrint: Long)(val name: String, val dress: String)
defined class Person
scala> val j = Person(123123l)("Joe","Suit")
j: Person = Person(123123)
scala> j.name
res: String = Joe
- 當然用
copy
的時候,也就知道後面curried
的部份也沒有實作copy
,所以你必需帶所有的參數。
scala> j.copy()(name = "David")
<console>:25: error: not enough arguments for method copy: (name: String, dress: String)Person.
Unspecified value parameter dress.
j.copy()(name = "David")
^
scala> j.copy()(name = "David", dress = "Dirty")
res80: Person = Person(123123)
- 其他的像是
tupled
當然也都不能用嘍!
因為case class繼承Product
這個trait
而所得到的能力
def productArity: Int
,這個function可以得到這個case class的object有多少個properties。
scala> joe.productArity
res: Int = 3
// 這3個就是region, id, name
def productElement(n: Int): Any
,取得某個指定的Element。注意:這裡的回傳type是Any哦!
scala> joe.productElement(2)
res: Any = Joe
def productIterator: Iterator[Any]
,把每個properties iterate出來。
這裡提醒一些不熟scala的朋友,iterator這裡若用map會發現怎麼沒有被執行到,要先toList
或是用foreach才會執行,這是因為iterator的特性。
scala> joe.productIterator.foreach(println)
TW
123
Joe
def productPrefix: String
,以字串的形式取得case class object的型態。
scala> joe.productPrefix
res: String = User
沒有留言:
張貼留言