かとじゅんの技術日誌

技術の話をするところ

ScalaのクラスをJavaの視点で解剖する

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の文化は必要だというのはよくわかりますね。

*1:ScalaObjectクラスはScalaのクラスに実装されるインターフェイスですかね。よくしらない。