Scalaのクラスをちょっと解剖して、なぜそうなっているか読み取ってみようと思います。
ScalaのクラスをJavaのクラスに変換してみる
まず、こんなScalaのクラスを作ってみた。コンストラクタに氏と名のフィールドを二つ取る単純な人名クラスです。
class PersonName以降のカッコのところは、コンストラクタの引数を表しています。また、その引数そのものがフィールド定義となります。
package test class PersonName(val firstName:String, val lastName:String)
これをscalacでクラスファイルにした後、jadでデコンパイルしてJavaのソースファイルにしてみました。以下のJavaコードで読みとくと簡単に理解できると思います。*1
package test; import scala.ScalaObject; public class PersonName implements ScalaObject { private final String firstName; // val firtName:String private final String lastName; // val lastName:String public PersonName(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public String firstName() { return firstName; } public String lastName() { return lastName; } }
蛇足
getterに"get"が欲しければ以下のように書いてあげるともれなくgetterがプレゼントされます。
class PersonName(@BeanProperty val firstName:String, @BeanProperty val lastName:String)
まぁ、余分に作られますが、無いよりマシかもしれません。
public String firstName() { return firstName; } public String getFirstName() { return firstName(); } // lastName, getLastNameも同様に作られる。
valフィールド=finalフィールド
話を元に戻しますが、Scalaでは変数宣言は原則的にvalで記述します。今回のクラスもvalでフィールド宣言しています。
Scalaのクラスのデコンパイルした結果を見るとわかりますが、valフィールドがfinalフィールドになっています。
これまでのメモリモデルの話でも説明したように、finalフィールドはコンストラクト後に完全に初期化され、スレッドから見えることが保証されています。また、初期化後は同期化が不要でスレッドセーフです。(メモリバリアとしては、finalフィールドのストア後にStoreLoadバリアを発生させます。)
そういう意味では、並行処理でマルチコアのパワーをなるべく引き出さなければならないプログラムでは、finalフィールドは使って不変オブジェクトを作ることが多いので、"finalのつけ忘れのない"valの文化は必要だというのはよくわかりますね。