久しぶりにScalaの話題です。
タイトル通り、varなのに再代入できない症状でハマりました。場所はコンストラクタです。
こんなコードが危ない
使ったのは以下のようなコードです。
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はコンストラクターで指定された値が入って変更も可能となります。
匿名
画面が真っ暗、でもカーソルは出てる状況。
探して、ここにたどり着きました。
パスワード入力で、復活!
修理に出す寸前でした。ホントにありがとう!