ケースクラスの2つ目の引数が外から見えない、なぜだろう?ということが発端となり、いろいろと調べたりGitterで質問してみました。その結果、コンストラクタ引数の定義のしかたによって見え方(アクセス修飾子の状態)が異なるということがわかりました。
scala> case class A(x: Int)(y: Int){
| def printY() = println(y)
| }
defined class A
scala> val a = A(10)(20)
a: A = A(10)
scala> a.x
res22: Int = 10
scala> a.y
<console>:19: error: value y is not a member of A
a.y
^
scala> a.printY()
20
クラスののデフォルトコンストラクタ引数はprivateです。したがって、下の例ではprivateなフィールドxにアクセスしようとしてエラーが出ています。
scala> class A(x: Int)
defined class A
scala> new A(10)
res0: A = A@af23093
scala> res0.x
<console>:13: error: value x is not a member of A
res0.x
^
scala>
私は普段case classをよく使う一方、クラスのフィールドに外からアクセスすることはあまりないので、すっかり頭のなかから抜け落ちていました。
このフィールドをpublicなものとするには、 val
または var
を明示的に付けてあげる必要があります。
scala> class A(val x: Int)
defined class A
scala> new A(10)
res0: A = A@6deaf732
scala> res0.x
res1: Int = 10
あるいは、case classとすると、valやvarを付加しなくても自動生成されたゲッターメソッドによってフィールドにアクセスできます。
scala> case class A(x: Int)
defined class A
scala> A(10)
res0: A = A(10)
scala> res0.x
res1: Int = 10
しかしながら、case classは万能ではありません。case classに複数のパラメータリストが存在する場合、2つ目以降のパラメータリストにはゲッターメソッドを生成してくれません。
scala> case class B(x: Int)(y: Int)(z: Int)
defined class B
scala> B(10)(20)(30)
res0: B = B(10)
scala> res0.x
res1: Int = 10
scala> res0.y
<console>:14: error: value y is not a member of B
res0.y
^
scala> res0.z
<console>:14: error: value z is not a member of B
res0.z
^
それどころか、case classはequals()やhashCode()についても2つ目以降のパラメータリストを無視してくれるので、以下のように「第一パラメータだけで比較する」「ハッシュコードが同一になる」といった現象が起こります。
https://gitter.im/scalajp/public
scala> case class C(x: Int)(y: Int)
defined class C
scala> C(10)(20)
res0: C = C(10)
scala> C(10)(30)
res1: C = C(10)
scala> res0 == res1
res2: Boolean = true
scala> res0.hashCode
res3: Int = -2008924253
scala> res1.hashCode
res4: Int = -2008924253
結論:気をつけます。
参考
http://www.ne.jp/asahi/hishidama/home/tech/scala/class.html#h_construntor
http://www.ne.jp/asahi/hishidama/home/tech/scala/class.html#h_case_class