Scalaスケーラブルプログラミング第4章
4.1. クラス、フィールド、メソッド
はじめに
- この章ででてくるChecksumってなに?
- https://wa3.i-3-i.info/word1240.html
チェックサム (checksum)とは
「送ったデータ大丈夫?」ってチェックするための方法のひとつ。
もしくは
そのチェック用にくっつけてやる「送るデータを全部足した値」のこと
-
シングルトンオブジェクト何に使うの?
- メリットはよくわからんがそう言うものだと思えばいいのかな。。。
-
4章では2つのファイルを書きます
- ChecksumAccumulator.scala
- Summer.scala
-
この2つがあると、p87の下記が実行できるようになる
$ scala Summer of love
- Scalaのクラスとオブジェクトの基礎、
- アプリケーションのコンパイル、実行方法について学ぶ。
- クラスとは
- オブジェクトの青写真(「図面」とか訳した方がいいのでは?)
- クラスからオブジエクトを作成できる
- クラス定義の書き方は↓
ChecksumAccumulator.scala
class ChecksumAccumulator{
//ここにクラス定義
}
- これがあると、下記のようにオブジェクトを作成できる
-
new
をつけてclass
を呼べば良い
-
new ChecksumAccumulator
クラス定義に書くこと
-
クラス定義の中には何を書けば良いか?
- フィールドとメソッドの2つ。まとめてメンバー
-
フィールドとは?
- オブジェクトを参照する変数。
val
,var
で定義 - オブジェクトのデータや状態を保持
- オブジェクトを参照する変数。
-
メソッドとは?
- 実行可能なコードを格納。
def
で定義 - データを使って、オブジェクトが行うべき仕事を実行
- 実行可能なコードを格納。
-
クラスのインスタンス生成時にメモリーが確保される(これ意識するのか?)
-
フィールドを追加して見ると↓
ChecksumAccumulator.scala
class ChecksumAccumulator{
var sum = 0 //フィールド
}
- インスタンスを2個作ると↓
val acc = ChecksumAccumulator
val csa = ChecksumAccumulator
- この2つのインスタンスを見ていこう
メモリ内のオブジェクトイメージはどうなるか?
-
acc
とcsa
の2つのオブジェクトができる。- それぞれ、
sum
というフィールドを持っている。 - それぞれの
sum
というフィールドは独立していて、一方に値を再代入してももう一方は変わらない - インスタンス専用の変数のため「インスタンス変数」とも呼ばれる。
- それぞれ、
-
acc
はval
なので書き換えられない。一方、sum
はvar
なので書き換えられる。-
acc
が初期化時に代入されたのと同じChecksumAccumulator
であることは確定。 - 一方、フィールド(
sum
)は変化しているかも。
-
- オブジェクトを堅牢に保つには?
- フィールド(
sum
)を非公開にする。 - するとクラス外からの直接的なアクセスできない。
- 非公開フィールドは同じクラスで定義されたメソッドだけしかアクセスできないので、情報を更新するコードはクラス内のみ。
- フィールド(
- 非公開にするには、フィールドの前に
private
というアクセス修飾子を挿入すれば良い- ちなみに、scalaはデフォルト公開
ChecksumAccumulator.scala
class ChecksumAccumulator{
private var sum = 0 //privateになった!
}
- クラスの外から
sum
にアクセスできない↓
val acc = ChecksumAccumulator
acc.sum = 5 //非公開のためアクセスできない
Class内でsum
をいじる
-
sum
にアクセスするには、クラスの本体で定義されたコードのみ。-
add
とchecksum
を追加してみる
-
ChecksumAccumulator.scala
class ChecksumAccumulator{
private var sum = 0
def add(b: Byte): Unit = {
sum += b
}
def checksum(): Int = {
return ~(sum & 0xFF) + 1
}
}
- メソッドパラメータ(上記の
b
)はval
のため、メソッド内で書き換えられない - 上記を単純化する4つの手法
-
return
文は除く
2.return
なければメソッド内で計算された最後の値を返す
3. 複数のreturn
は書かないこと推奨 - メソッドが単一の結果式を計算するだけなら、中かっこ省略可
- 結果式が短ければ、
def
と同じ行に書ける - 結果型を省略し、型推論を利用可能(下記ではしてない)
5. しない理由:public
メソッドの結果型を明示するのは多くの場合適切
ChecksumAccumulator.scala
class ChecksumAccumulator{
private var sum = 0
def add(b: Byte): Unit = { sum += b } //中括弧を省略しないのは視認性のため?
def checksum(): Int = ~(sum & 0xFF) + 1
}
- 結果型がUnitのメソッドは、副作用を生じさせるために実行。
- 副作用だけを目的として実行されるメソッドは、手続き
4.2. セミコロン推論
- 本来、文の末尾にはセミコロンを入れるべき。
- Scalaはセミコロンを類推してくれるので、原則、文の末尾のセミコロンは入れても入れなくて良い
- 1行に複数の文を書くときは必要
val s = "hello"; println(s)
- 複数行にまたがる時、正しい位置で分割してくれる(類推をうまくやってくれている)
- 下記は4行だが、1つの文章として扱われる
if (x < 2)
println("too small")
else
println("ok")
// 下記のルールのどれに当てはまる??
- 類推のロジックは、下記の条件に当てはまらなければ、行末はセミコロンとして扱われる。
-
- 当該行の末尾が、文法的に認められていない単語になっている(ピリオド、中置演算子)
-
- 次の行が文の先頭として認められてない単語になっている
-
- かっこや、角かっこの中にいる状態で文末になっている
-
x
+ y
// 1,2,3どれにも当てはまらないので、行末はセミコロンとして扱われる→1文でない
(x
+ y)
// 3 に当てはまるため、行末はセミコロンでない→1文として扱われる
x +
y +
z
// 1 に当てはまるため、行末はセミコロンでない→1文として扱われる。
4.3 シングルトンオブジェクト
- ScalaはJavaよりオブジェクト指向
- 理由:クラスが静的メンバーを持てないから
- Scalaにはシングルトンオブジェクトがある
- シングルトンオブジェクトは、クラス定義とよく似ている
-
class
の代わりにobject
を用いる
import scala.collection.mutable // 変更できる方
object ChecksumAccumulator { //シングルトンobject!下記のクラスと同じ名前。「コンパニオンオブジェクト」と呼ぶ。同じソースファイルで定義必要。
private val cache = mutable.Map.empty[String, Int]
def calculate(s: String): Int =
if (cache.contains(s))
cache(s)
else {
val acc = new ChecksumAccumulator //下のコンパニオンClassのインスタンスを作成
for (c <- s) // 文字列を1字ずつ処理
acc.add(c.toByte) // 1字をbyteに変換し、accのメソッドaddで非公開フィールドsumに足し上げ
val cs = acc.checksum() // accのchecksumを実行
cache += (s -> cs) // キー(s)と整数値(cs)をcacheマップに追加
cs // calculateの結果として返す
}
}
//下記が省略されてるか
class ChecksumAccumulator{ //シングルトンobjectのコンパニオンclass
private var sum = 0
def add(b: Byte): Unit = { sum += b }
def checksum(): Int = ~(sum & 0xFF) + 1
}
- シングルトンオブジェクト、コンパニオンクラスは互いの非公開メンバーにアクセスできる(
private
のついているもの)- 上記では特にアクセスしてない?
- 実行は下記で行う
ChecusumAccumulator.calculate("Every value is an object.")
- シングルトンオブジェクトは一人前のオブジェクト
- シングルトンオブジェクトの名前(
ChecksumAccumulator
)はネームタグ(p79のacc
と比較)
- シングルトンオブジェクトの名前(
- シングルトンオブジェクトの定義だけがあっても、
ChecksumAccumulator
型の変数は作ることはできない- コンパニオンクラスがないといけない
- シングルトンオブジェクトは、スーパークラスを継承し、トレイトをミックスインできる。(スーパークラスとはコンパニオンクラスとは別?)
- これらの型のメソッド、変数、パラメーターを活用できる
- クラスはパラメータを取れるが、シングルトンオブジェクトはパラメータを取れない
- シングルトンオブジェクトは
new
キーワードでインスタンス生成できないのでパラメーターを取れない(p60がパラメータの例か) - 個々のシングルトンオブジェクトは自動生成クラスのインスタンスとして実装。
- 初期化瀬マンティクスを持っている。
- シングルトンオブジェクトは何らかのコードから初めてアクセスされたときに初期化される。(
new
しなくてもインスタンスができる?)
- シングルトンオブジェクトは
- コンパニオンクラスと同じ名前を共有しないシングルトンオブジェクトは、「スタンドアロンオブジェクト」
4.4 Scalaアプリケーション
- Scalaプログラムを実行するには、
-
main
メソッドを持つ、 - スタンドアロンシングルトンオブジェクト(コンパニオンクラスがないシングルトンオブジェクト)
-
- の名前を指定しなければならない
-
main
メソッドは- パラメータとして
Array[String]
- 結果型は
Unit
- パラメータとして
- 適切なシグネチャーの
main
メソッドを持つスタンドアロンオブジェクトはどれでもアプリケーションのエントリーポイントして使うことができる(謎)
Summer.scala
import ChecksumAccumulator.calculate // import している
object Summer {
def main(args: Array[String]) = { // mainメソッド、Array[String]、Unit型
for (arg <- args) // 引数を1つずつ
println(arg + ": " + calculate(arg)) // 単語を表示し、そのchecksumを返す
}
}
- 上記は
-
main
メソッドを持つ,スタンドアロンシングルトンオブジェクトがある -
main
メソッドはパラメータがArray[String]
,結果型はUnit
(省略されているが)
-
-
import
文はChecksumAccumulator
オブジェクトのcalculateメソッドをインポートしている- インポートすると、メソッドの単純名を使えるようになる
- ちなみに、Scalaは
Predef
と言うシングルトンオブジェクトのメンバーを暗黙農地に全てのScalaソースファイルにインポートしている。println
やassert
はこのオブジェクトのメソッド。
# 完成したので実行しよう
- ここまでで、
- ChecksumAccumulator.scala
- Summer.scala
- を作成した。
- ファイル名はどうつけるべきか?
- .scalaファイルには好きな名前をつけることが可能
- スクリプトでないファイルの場合は、格納しているクラスと同じ名前をつけるスタイルが推奨
- Check~.scalaとSummer.scalaは、定義で終わっているのでスクリプトではない
- スクリプトは最後が結果式
- スクリプトでないファイルは、Scalaコンパイラーでコンパイルし、生成されたクラスファイルを実行する必要
- コンパイルを行うには基本コンパイラー
scalac
- 待ち時間がある
- コンパイラーを起動するたびに、jarファイルの内容をスキャンするなどの初期作業に時間を費やしてから、ユーザーが指定したソースファイルを読むから
$ scalac ChecksumAccumulator.scala Summer.scala
-
fsc
というコンパイラーデーモンもある- 良くわからんが、1回実行すれば、2回目以降の実行時間が削減される
$ fsc ChecksumAccumulator.scala Summer.scala
- scalacかfscを実行すると、Javaクラスファイルが生成され、それをscalaコマンドで実行できる
- mainメソッドを格納するスタンドアロンオブジェクトの名前を指定する
$ scala Summer of love // Summerはスタンドアロンオブジェクトの名前
4.5 Appトレイト
- scala.Appと言うトレイトを提供して、コード入力そ少しでも減らせるようにしている(謎)
import ChecksumAccumulator.calculate
object FallWinterSpringSummer extends App {
for (season <- List("fall","winter","spring"))
println(season + ": " + calculate(season))
}
- 書き方
- シングルトンオブジェクトの名前の後に
extends App
を書く -
main
メソッドを書かずに、それに入れるはずだったコードを中括弧の間にかく -
args
と書けば、コマンドライン引数にアクセスできる
- シングルトンオブジェクトの名前の後に
- 他のアプリと同じようにコンパイル、実行できる(
def main
を書かずに済むので便利、と言うことか?)