Scala自分用メモ


ScalaのSeqは基本的に不変のコレクション

値を追加したいときは、seq :+ <追加したい値> のようにして、要素が追加された新しいSeqを作成する

索引型・非索引型検索

索引型検索 あらかじめ作られた索引をもとに検索("word" -> P1, P24)
非索引型検索 与えられた検索パターンを対象全てに試して検索 

map関数の引数で、_.とすると、自身のメソッドを呼び出せる
```sbtshell
scala> val testList = Seq("Oh", "Nagashima")
testList: Seq[String] = List(Oh, Nagashima)

scala> testList.map(_.slice(0,2))
res0: Seq[String] = List(Oh, Na)
```

String型.r で正規表現のRegex型に変換できる

.r.findAllIn(<検索対象>) で、MatchIterator型のオブジェクト取得

コンストラクタ引数は、 val や var という変数宣言を与えてやることで、インスタンスから呼び出すことができる

def hogehoge(num: Int): Unit = ??? このように書くと、メソッドの中身書いていなくてもコンパイルできる

extends Appをつけると、intellij上でRunできるようになる

objectキーワードを使って、「クラスのインスタンス化」ではない方法でオブジェクトを呼び出してフィールドやメソッドを利用することができる

class Dog(name: String) {
  def greet(): Unit = println(s"${name}だワン")
}

object Taro extends Dog("タロウ")

ファクトリメソッド(applyメソッドを使ったクラスのインスタンスを生成するメソッド)で、コンストラクタ引数の型を変えたりしてインスタンス作成の方法を複数設定できる

class Dog(private val name: String) {
  def greet(): Unit = println(s"${name}だワン")
  override def toString = s"${name}という名前の犬です"
}


object Dog { // ファクトリメソッド(クラスのインスタンスを生成するメソッドのこと)

  def apply(name: String) = new Dog(name)

  def apply(id: Int) = new Dog(s"番号付き犬${id}番")

  def printName(dog: Dog): Unit = println(dog.name)
}
scala> val taro = Dog("タロウ")   << val taro = new Dog("タロウ") と同じ処理
taro: Dog = タロウという名前の犬です  << Stringを渡すと、1つ目のapplyが適用される

scala> val dog3 = Dog(3)
dog3: Dog = 番号付き犬3番という名前の犬です   << Intを渡すことで、2つ目のapplyが適用された

クラスと同名のオブジェクトのことを「コンパニオンオブジェクト」と呼ぶ。
コンパニオンオブジェクトは、同名クラスのプライベートメンバーにアクセス可能

ちなみに、intelliJでは、クラスのスコープ内で右クリック Generate -> toString()から簡単にtoStringを定義できる

ケースクラス

class Point(val x: Int, val y: Int) {

  override def toString = s"Point($x, $y)"
}
object Point {
  def apply(x: Int, y: Int) = new Point(x, y)
}

は、以下で簡単に書き換えられる。
scala
case class CPoint(x: Int, y: Int)

toStringも勝手に、"クラス名(attribute1, attribute2 ...)"という形式で定義してくれる
newをつけなくても、val point1 = Point(2, 3)という形でインスタンス生成できる(objectの定義や、その中でのapplyメソッドの実装不要)
valが不要(勝手にメンバーを公開)

  ※ var で宣言するような更新可能な属性は持たない。値を変えた新しいオブジェクトを生成するには、copyメソッドを使う。

val cp1 = CPoint(5, 6)
val cp2 = cp1.copy(7, 8)
val cp3 = cp2.copy(y = 9)

クラスとケースクラスでは、同値性の判断が異なる

scala> val c1 = new Point(1, 2)
val c2 = new Point(1, 2)
val c3 = new Point(3, 4)
println("c1 == c1: " + (c1 == c1))
println("c1 == c2: " + (c1 == c2))
println("c1 == c3: " + (c1 == c3))
c1: Point = Point(1, 2)

scala> c2: Point = Point(1, 2)

scala> c3: Point = Point(3, 4)

scala> c1 == c1: true

scala> c1 == c2: false   // クラスの場合は、値があっていたとしても、別のインスタンスなのでfalse

scala> c1 == c3: false

scala> val cc1 = new CPoint(1, 2)
val cc2 = new CPoint(1, 2)
val cc3 = new CPoint(3, 4)
println("cc1 == cc1: " + (cc1 == cc1))
println("cc1 == cc2: " + (cc1 == cc2))
println("cc1 == cc3: " + (cc1 == cc3))
cc1: CPoint = CPoint(1,2)

scala> cc2: CPoint = CPoint(1,2)

scala> cc3: CPoint = CPoint(3,4)

scala> cc1 == cc1: true

scala> cc1 == cc2: true  // ケースクラスの場合は、値のみを見て同値性を判断するので、true

scala> cc1 == cc3: false

ちなみに、クラスでも、IDEを使ってGenerate -> equals() and hashCode() からケースクラスと同じ挙動をとるよう設定できる

モジュール間の依存関係は、一方向のほうがよい。柔軟性のため。

package object

パッケージオブジェクトに実装したメソッドは、同じパッケージ内であれば、import文なしで、呼び出せる
(パッケージオブジェクトの名前は、パッケージと同じにする)

まとめてimport

import jp.co.dwango.marubatsu.board.CellState
import jp.co.dwango.marubatsu.board.Empty
import jp.co.dwango.marubatsu.board.Maru
import jp.co.dwango.marubatsu.board.Batsu

以上は以下のように省略できる
scala
import jp.co.dwango.marubatsu.board.{CellState, Empty, Maru, Batsu}

アクセス修飾子まとめ

アクセス修飾子 意味
private 自クラス及びコンパニオンオブジェクトからアクセスできる
private[this] 自クラスの同一のインスタンスからのみアクセスできる
private[パッケージ名/クラス名] 指定されたパッケージ/クラスからアクセスできる
protected 自クラスまたはサブクラスからアクセスできる
protected[this] 自クラスまたはサブクラスの同一インスタンスからアクセスできる
protected[パッケージ/クラス名] 指定したパッケージ/クラスであるサブクラスからアクセスできる

トレイト  (「特性、特色」という意味) (抽象クラスとの違い)

クラスの定義で、複数のトレイトをミックスインすることができる <=> 抽象クラスは、複数を継承させることはできない
コンストラクタを持つことはできない <=> 抽象クラスは、クラスなので当然コンストラクタを定義できる

trait Fillable {
  def fill(): Unit = println("Fill!")
}

trait Disposable {
  def dispose(): Unit = println("Dispose!")
}


class Paper

class PaperCup extends Paper with Fillable with Disposable {

}

直接インタンス化はできない(「特性」そのものは、具象化できない)

val f = new Fillable()  エラー

ただし、{}をつけて(中に定義を書くこともできる)、匿名内部クラスとしてインスタンス化することは可能

val fff = new Fillable(){}

菱型継承問題

複数のトレイトをミックスインさせる際、同名のメソッドなどが衝突するしてしまう問題。
Scalaでは、コンパイル時にエラーを出す。

対処法

ミックスインさせる際に、衝突しているメソッドをoverrideする
衝突しているトレイトのメソッド定義時に、override をつける。そうすると、線形化機能が働き、with句の一番最後のトレイトのメソッドが採用される。

複数のモジュールで共通のインスタンスを扱うとき

オブジェクトやクラス、機能別でファイルを分ける際のデザイン

例: 動画プレイヤーのプロジェクトについて、柔軟性を高めるため、可読性を高めるためのリファクタリングとしてファイルを以下のように分ける。

動画一覧のテーブルを司るモジュール
再生、停止を扱うモジュール
マウスイベントハンドラ

しかし、動画プレイヤー全体として操作したいインスタンス(動画プレイヤーインスタンス、テーブルインスタンスなど)
は、当然「同じ」オブジェクトである。

つまり各モジュールでインスタンスを生成することは絶対に避ける必要がある。

そうしたときに、インスタンスにアクセスする方法として、

引数にオブジェクトインスタンスをとるメソッドを実装する

という方法がある。(というかこれ以外にない?)