[Scala] varなのに再代入できない!

  • 投稿日:
  • 更新日:2019/03/09
  • by
  • カテゴリ:

久しぶりにScalaの話題です。

タイトル通り、varなのに再代入できない症状でハマりました。場所はコンストラクタです。

Scala_logo.png

こんなコードが危ない

使ったのは以下のようなコードです。

class Super(var foo: Int)
class Sub(foo: Int) extends Super(foo) {
foo = 100
}

コードの意図は、単に「スーパークラスでvarに定義したfooを、サブクラスで変更する」というものです。

上記をコンパイルしようとすると、

error: reassignment to val

と怒られます。

fooはvarなのになんで?

class Super(var foo: Int)
class Sub(foo: Int) extends Super(foo)

としておいて、

a = new Sub(10)  // a.foo == 10
a.foo = 100 // a.foo == 100

と、外部からfooの値を書き換えるのは大丈夫なので、さらに混迷の度が深まります。

種明かし

結構はまったのですが、これは以下のことが原因だということがわかりました。

基本コンストラクターの引数は、valやvarをつけない場合、暗黙にprivateな不変フィールド(valのフィールド)となる。

つまり、クラスSubでは、

private val foo

が定義されていたため、fooの変更が許されなかったわけです。

これをなんとかしようと、Subのコンストラクタ変数にvarなどをつけても無駄です。

class Sub(var foo: Int) extends Super(foo)

これだと

error: overriding variable foo in class Super of type Int;
variable foo needs `override' modifier
class Sub(var foo: Int) extends Super(foo)
^

「overrideなしで勝手にオーバライドすな!」怒られますし、それならばと

class Sub(override var foo: Int) extends Super(foo)

とすると、今度は

error: overriding variable foo in class Super of type Int;
variable foo cannot override a mutable variable
class Sub(override var foo: Int) extends Super(foo)
^

「fooは変更可能な変数でオーバーライドしたらいかん!」怒られます。

ちょっと途方にくれました。

解決法

こいつの解決法は単純で、

「変数名を変える」

ということです。

class Sub(bar: Int) extends Super(bar) {
foo = 100
}

これなら変数名が衝突しないので、何も怒られません。

細かいことを言うと、barはprivateなval変数(変更不可能な変数)としてクラスSubからのみ参照可能なものとして使える一方、fooはコンストラクターで指定された値が入って変更も可能となります。

こちらもよく読まれています