ScalaのSeqは基本的に不変のコレクション
値を追加したいときは、seq :+ <追加したい値> のようにして、要素が追加された新しいSeqを作成する索引型・非索引型検索
索引型検索 あらかじめ作られた索引をもとに検索("word" -> P1, P24) 非索引型検索 与えられた検索パターンを対象全てに試して検索map関数の引数で、_.とすると、自身のメソッドを呼び出せる
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)
}
は、以下で簡単に書き換えられる。
case class CPoint(x: Int, y: Int)
※ 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
以上は以下のように省略できる
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句の一番最後のトレイトのメソッドが採用される。複数のモジュールで共通のインスタンスを扱うとき
オブジェクトやクラス、機能別でファイルを分ける際のデザイン
例: 動画プレイヤーのプロジェクトについて、柔軟性を高めるため、可読性を高めるためのリファクタリングとしてファイルを以下のように分ける。
動画一覧のテーブルを司るモジュール 再生、停止を扱うモジュール マウスイベントハンドラしかし、動画プレイヤー全体として操作したいインスタンス(動画プレイヤーインスタンス、テーブルインスタンスなど)
は、当然「同じ」オブジェクトである。
つまり各モジュールでインスタンスを生成することは絶対に避ける必要がある。
そうしたときに、インスタンスにアクセスする方法として、
引数にオブジェクトインスタンスをとるメソッドを実装する
という方法がある。(というかこれ以外にない?)