「Scalaスケーラブルプログラミング」で気になったところまとめ ~ 4章&5章 ~

この記事は?

Scalaスケーラブルプログラミング第3版

Scalaスケーラブルプログラミング第3版

Scalaスケーラブルプログラミング」を読んで、個人的に気になった箇所や勉強になった箇所、逆によくわからなかった箇所のまとめです。 認識誤りなどがあればご指摘いただけると幸いです。

第4章 クラスとオブジェクト

クラス定義は基本的にJavaと同じ

Scalaでは以下のようにクラスを定義する

class ChecksumAccumulator {
    // ここにクラス定義を書く
}

オブジェクトを作成するときはnewを使う

val ca = new ChecksumAccumulator

valで作成したオブジェクトに別のオブジェクトを代入することはできない

scala> val hoge = List("hoge","fuga")
hoge: List[String] = List(hoge, fuga)

scala> hoge = List("foo", "bar")
<console>:8: error: reassignment to val
       hoge = List("foo", "bar")
            ^

Scalaでは明示的にアクセス修飾子を指定しなければメンバーはpublicになる

アクセス修飾子をprivateにした場合
// 例で使用するクラス
class ChecksumAccumulator {
    private var sum = 0 
}
scala> val ca = new ChecksumAccumulator
ca: ChecksumAccumulator = ChecksumAccumulator@60dd0587

scala> ca.sum = 5 // privateなのでアクセスできない
<console>:12: error: variable sum in class ChecksumAccumulator cannot be accessed in ChecksumAccumulator
       ca.sum = 5
          ^
<console>:13: error: variable sum in class ChecksumAccumulator cannot be accessed in ChecksumAccumulator
       val $ires0 = ca.sum
                       ^
アクセス修飾子を指定しなかった場合
// 例で使用するクラス
class ChecksumAccumulator {
    var sum = 0 
}
scala> val ca = new ChecksumAccumulator
ca: ChecksumAccumulator = ChecksumAccumulator@60dd0587

scala> ca.sum = 5  // アクセス修飾子を指定していない(public)なのでアクセス可能
ca.sum: Int = 5

メソッドのパラメータはvalである

パラメータがvalなのは、valの方が推論に便利だかららしい。(p.081)

// 例
class ChecksumAccumulator {
    private var sum = 0
    def add(b: Byte): Unit = {
        b = 1  // bはvalなので代入できない。コンパイルエラーになる。
        sum += b
    }
}

Scalaにはシングルトンオブジェクトと呼ばれるものがある

Scalaではクラスが静的メンバー(static member)を持つことができない。その代わりにシングルトンオブジェクトと呼ばれるものを持つことができる。

シングルトンオブジェクトはclassキーワードの代わりにobjectキーワードを使う。

object ChecksumAccumulator {
  // ここに定義を書く
}

クラスとシングルトンオブジェクトが同じ名前を持つ時、そのクラスのコンパニオンオブジェクトと呼ぶ。

逆にクラスはそのオブジェクトのコンパニオンクラスと呼ぶ。

ちなみにコンパニオンクラスと同じ名前ではないシングルトンオブジェクトをスタンドアロンオブジェクトと呼ぶ。

クラスとそのコンパニオンオブジェクトはお互いのprivateメンバーにアクセスできる

class Hoge {
    private val name = "hoge"
}

object Hoge {
    def printHogeName(): Unit = {
        val hoge = new Hoge
        println(hoge.name)  // クラスとオブジェクトが同じ名前なのでprivateメンバーにアクセスできる
    } 
}
class Hoge {
    private val name = "hoge"
}

object Fuga {  // クラスとオブジェクトが同じ名前ではないのでエラーになる
    def printHogeName(): Unit = {
        val hoge = new Hoge
        println(hoge.name)
    } 
}

ちなみにクラスとオブジェクトが同じ名前じゃない場合は以下のようなエラーになります

// 上の例の場合
<console>:18: error: value name in class Hoge cannot be accessed in Hoge
            println(hoge.name)

Scalaプログラムを実行する方法

Scalaプログラムを実行するにはmainメソッドを持つスタンドアロンオブジェクトを作る必要がある。

object Hoge {
    def main(args: Array[String]) = {
        for (arg <- args)
            println("arg = " + arg)
    }
}

コンパイルを行うにはscalacコマンドを使用する。 実行にはscalaコマンドを使用する。

$ scalac Hoge.scala

$ scala Hoge foo bar baz
arg = foo
arg = bar
arg = baz

Appトレイトを使うとmainメソッドを省略できる

// Appトレイトを使うとmainメソッドを省略できる
object Hoge extends App {
    for (arg <- args)
        println("arg = " + arg)
}

第5章 基本型と演算子

※基本的にJavaと変わらないため、Javaとの違いを中心に書いています。

ダブルクォートを3個続けると生の文字列を表現できる

特殊文字を文字列に含めたい場合、普通はエスケープしないといけないが、ダブルクォートを3つ使うと特殊文字エスケープしなくても出力などが可能になる。

// 通常の出力
scala> println("\\\"\'\\n\\t\\r")
\"'\n\t\r

// ダブルクォートを3つ使った場合
scala> println("""\"'\n\t\r""")
\"'\n\t\r

Scalaではシンボルリテラルを作ることができる

シンボルリテラルを作るには'シンボル名のように'をシンボル名の前に入れる。

同じシンボル名はすべて同じSymbolオブジェクトを参照するので少し注意が必要。

scala> val s = 'aSymbol
s: Symbol = 'aSymbol

Scalaでは文字列リテラルを加工して式に含めることができる

文字列の前にsを、文字列中に$を入れることで文字列に式を含めることができる

scala> s"The answer is ${6 * 7}"
res5: String = The answer is 42


scala> val name = "Luke"
name: String = Luke

scala> s"Use the Force $name"
res6: String = Use the Force Luke

fを使用すると書式を指定できる

// 書式を指定しない場合
scala> s"${math.Pi}"
res10: String = 3.141592653589793

// 書式を指定した場合
scala> f"${math.Pi}%.5f"
res11: String = 3.14159

Scalaでは演算子はメソッドである

Scalaでは+-といったメソッドだけでなくすべてのメソッドを演算子のように使うことができる

scala> val sum = 1 + 2 // 1.+(2)を呼び出している
sum: Int = 3

scala> val sumMore = 1.+(2)
sumMore: Int = 3


scala> val s = "Hello, World!"
s: String = Hello, World!

scala> s indexOf 'o' // s.indexOf('o')を呼び出している
res0: Int = 4


scala> -2.0
res1: Double = -2.0

scala> (2.0).unary_- //前置、後置演算子の場合はunary_[演算子]を呼んでいる
res2: Double = -2.0

またScalaの慣習ではメソッドが副作用を持つ場合()をつけ、副作用を持たない場合は()を省略する

scala> val s = "Hello, World!"
s: String = Hello, World!

// toLowerCaseには副作用がないので()はつけない
scala> s.toLowerCase
res3: String = hello, world!

オブジェクトが等価どうかを比較する場合は==を使う

Scala==Javaequalsと同じで、 ScalaeqJava==と同じ

scala> List(1,2,3) == List(1,2,3)
res6: Boolean = true

scala> List(1,2,3) eq List(1,2,3)
res7: Boolean = false