前提
Scala2系です。
3系だとパッケージオブジェクトとかtraitのwithとか違うと思います
暗黙の了解
Scalaのファイル名はアッパーキャメルケースを使用します。(Main.Scala・Index.Scala・・・)
オブジェクト
オブジェクト:状態・処理を保持できる機構
構文
// Mainというオブジェクトを作成する
object Main
トレイト
トレイト:振る舞いを定義するもの
ミックスイン:トレイトで定義した振る舞いをオブジェクトに追加すること
// AppトレイトをMainオブジェクトにミックスインしている
object Main extends App { }
変数・定数
変数: var
を使う
定数: val
を使う
構文↓
object Repl extends App {
var hour = 24; // 変数
val minute = 60; // 定数
hour = 12; // OK
minute = 3600; //NG
}
テンプレートリテラル
文字列の前にs
、文字列中の変数に${変数名}
を与える
構文↓
object Repl extends App {
val hour = 24;
println(s"1日は${hour}時間です。") // 1日は24時間です。
}
関数定義
def 関数名(引数名: 型): 戻り値の型 = { }
例↓
object Repl extends App {
def callMyName(name: String) = {
println(s"私の名前は${name}です。")
}
callMyName("warabimochi") // 私の名前はwarabimochiです。
}
可変長引数の定義・呼び出し
可変長引数の定義は引数の型に*
を付ける
配列・シーケンスを渡したい時は: _*
を付けて展開してわた
object Repl extends App {
def kahentyouhikisuu(nums: Int*): Unit = {
println(nums)
}
kahentyouhikisuu(1, 2) // ArraySeq(1, 2)
kahentyouhikisuu(Array(1, 2, 3 ,4): _*) // ArraySeq(1, 2, 3, 4)
}
関数呼び出し
引数が無い関数の呼び出しは()を省いても良い
例↓
object Repl extends App {
def temp() = {
println("temp関数が呼び出されました。")
}
temp // temp関数が呼び出されました。
}
シーケンス(Seq)
Scalaで良く使う使いやすいメソッドを持つ配列
シーケンスは任意の添え字の値を変更出来ないので更新時は:+
を使う
object Repl extends App {
val sequence = Seq(1, 2, 3)
println(sequence) // List(1, 2, 3)
println(sequence(0)) // 1
val updatedSequence = sequence :+ 4
println(updatedSequence) // List(1, 2, 3, 4)
sequence(0) = 4 // NG
}
マップ(map)
Scalaの連想配列
マップは不変なので更新時はmap変数 + Map()
を使う
object Repl extends App {
val map = Map("key1" -> 1, "key2" -> 2)
println(map) // Map(key1 -> 1, key2 -> 2)
println(map("key1")) // 1
val updatedMap = map + ("key3" -> 3)
println(updatedMap) // Map(key1 -> 1, key2 -> 2, key3 -> 3)
}
String
String:""
で囲むと文字列になる
String変数(添え字)
で文字を抜き出せる
object Repl extends App {
val str = "abcde"
println(str(0)) // "a"
println(str.getClass()) // java.lang.String
}
Char
Char:''
で囲むと文字になる
object Repl extends App {
val char = 'a'
println(char.getClass) // char
}
if式
ifも式なので値を返せる
else句を書かないが、条件式がfalse
だった場合Unit
が返る
object Repl extends App {
var flag = true
val trueReturn = if(flag) {
"flag:true"
}
println(trueReturn) // flag:true
flag = false
val falseReturn = if(flag) {
"flag:false"
}
println(falseReturn) // ()
}
for式
Range型の値を複数for式に渡すとfor式一つで多重ループを実現できる
for式にif句を渡すとその条件の時だけループを回すフィルタリングが可能
for式に渡すループ条件はシーケンスとか配列も渡せる
object Repl extends App {
for(i <- 0 to 10) {
println(i) // 0, 1, 2... 10
}
for(i <- 0 until 10) {
println(i) // 0, 1, 2... 9
}
for(i <- 10 to 0 by -1) {
println(i) // 10, 1, 2... 0
}
for(i <- 0 to 2; j <- 0 to 2) {
println(s"i = ${i} j = ${j}") // i=0 j=0, i=0 j=1... i=2 j=2
}
for(i <- 0 to 2; j <- 0 to 2; if i == 0) {
println(s"i=${i} j=${j}") // i=0 j=0, i=0 j=1... i=0 j=3
}
for(i <- Seq("A", "B", "C", "D", "E")) {
println(i) // A, B, C... E
}
for(i <- Seq("A", "B", "C", "D", "E")) yield {
s"これは${i}です" // List("これはAです", "これは"Bです"... "これはEです"
}
}
while文
正確にはwhile式、こっちは一般的な感じ
式ではあるが常にUnit
しか返さないので文として使われる
object Repl extends App {
var i = 0;
while(i <= 10) {
println(i) // 0, 1, 2... 10
i = i + 1
}
}
タプル(Tuple)・分割代入
タプル:(値1, 値2...)
で書くやつ
(変数1, 変数2...)
とすることでタプルを変数に分割代入出来る
object Repl extends App {
val (num1, num2) = (1, 2)
println(num1) // 1
println(num2) // 2
}
{}式
ブロックで囲まれた式は最後の式が評価された値が返る
object Repl extends App {
val branceReturn = {
println("A")
1 + 2
}
println(branceReturn) // branceReturn:3
}
match式・パターンマッチ
match式: JavaScriptのswitch文みたいなモノ
case句に_
を使うとワイルドカードになる
case句に|
を使うと複数個指定できる
case句にSeq
・Array
等を渡すと要素を束縛して取り出せる
case句に変数名:型
を渡すと型でパターンマッチが行える
object Repl extends App {
val result = 3 match {
case 1 => "Java"
case 2 => "Scala"
case _ => "Kotlin"
}
println(result) // kotlin
def matchFunc(str: String) = {
str match {
case "good" | "good2" => {
println("引数はgoodかgood2です。")
}
}
}
matchFunc("good") // 引数はgoodかgood2です。
matchFunc("good2") // 引数はgoodかgood2です。
val sequence = Seq(1, 2, 3, 4, 5)
// コレクションの束縛
sequence match {
case Seq(i1, i2, i3, i4, i5) =>
println(i1, i2, i3, i4, i5) // 1, 2, 3, 4, 5
}
// ifガード句を使った束縛
sequence match {
case Seq(i1, i2, i3, i4, i5) if i1 != 1 => {
println(i1, i2, i3, i4, i5)
}
case _ => {
println("該当するケースが存在しませんでした。") // 該当するケースが存在しませんでした。
}
}
// asパターンでの束縛
sequence match {
case i@Seq(1, 2, 3, 4, 5) => {
println(i) // List(1, 2, 3, 4, 5)
}
}
val str: AnyRef = "文字列"
str match {
case i: Integer => println(s"${str}はint型です。")
case i: String => println(s"${str}はstring型です。") // 文字列はstring型です。
}
}
class
クラス: インスタンスを作る雛形
インスタンス: クラスから作られたオブジェクト
- フィールド: クラスが持つ状態
- メソッド: クラスが持つ振る舞い
- メンバー: フィールドとメソッドを合わせた呼び名
コンストラクタ: オブジェクトの生成と初期化を同時に行う時に利用するメソッド
コンストラクタ引数にval
・var
を付けることで外部にフィールドを公開する
カプセル化: メソッドにprivate
を付けると外部に公開しない
抽象クラス: abstract
を付けた継承するために使うクラス・インスタンス化出来ない
object Repl extends App {
class NormalCat(name: String) {
def greet(): Unit = println(s"僕、${name}")
}
val normalCat = new NormalCat("コンストラクタ引数がノーマルな猫")
normalCat.greet() // 僕、コンストラクタ引数がノーマルな猫
println(normalCat.name) // Error:value name is not a member of Repl.NormalCat
class ValCat(val name: String) {
def greet(): Unit = println(s"僕、${name}")
}
val valCat = new ValCat("コンストラクタ引数がvalな猫")
valCat.greet() // 僕、コンストラクタ引数がvalな猫
println(valCat.name) // コンストラクタ引数がvalな猫
class PrivateCat(name: String) {
private def greet(): Unit = println(s"僕、${name}")
}
val privateCat = new PrivateCat("メソッドがprivateな猫")
privateCat.greet() // Error
class NormalClass() {
println("normalClass")
}
val normalClass = new NormalClass() // normalClass
abstract class AbstractClass() {
println("abstractClass")
}
val abstractClass = new AbstractClass() // Error
}
継承(敢えてclassとは別に書きます)
継承とは継承元クラスのメンバー
を継承先クラスでも使えるようにする機能のこと
具体的には継承先クラス extends
継承元クラス(コンストラクタ引数
) と書く
継承元クラスにコンストラクタ引数
が存在しない場合は、継承時に()
を省く
継承元クラスにコンストラクタ引数
が存在する場合は、継承時にコンストラクタ引数
を渡す。
オーバーライド: 継承元クラスに存在するメソッド
を 継承先クラスに同じく定義することで、継承元クラスのメソッド
の挙動を上書きすること
具体的にはoverride def
のようにoverride
を前に付ける
object Repl extends App {
abstract class OyaClass {
val str = "親クラス1フィールドの文字列"
def funcTemp() = println("親クラス1のfuncTempが実行されました。")
}
class KoClass extends OyaClass
abstract class OyaClass2(str: String) {
val str2 = "親クラス2フィールドの文字列"
def funcTemp() = println("親クラス2のfuncTempが実行されました。")
}
class koClass2 extends OyaClass2("コンストラクタ引数が必要")
abstract class Parent {
def callClassName = println("このクラスはParentクラスです。")
}
class Child extends Parent {
override def callClassName = println("このクラスはChildクラスです。")
}
val child = new Child()
child.callClassName // このクラスはChildクラスです。
}
object
object
を使って作成したオブジェクトは、シングルトンオブジェクトになる
なので、インスタンス生成が不要
シングルトンオブジェクト: インスタンスが1つしか存在しないオブジェクト
object
にclass
を継承することで、new
を使わなくても、インスタンス生成が可能
object
のapply
メソッドは object名(引数) で呼び出せる
なので、コンパニオンオブジェクトを作成し、
apply
メソッドにファクトリメソッドを作ることが多い
コンパニオンオブジェクト: class名と同名のobject
ファクトリメソッド: インスタンスを生成する戻り値として返すメソッド
コンパニオンオブジェクトの中からなら同名class
のprivate
なメンバーを参照できる
以下のケースで使う
- グローバル変数・ユーティリティーメソッド
-
new
を使わないインスタンス生成 - コンパニオンオブジェクト・ファクトリメソッド
object SingletonObject {
var name = "warabimochi"
private val score = 100
def getScore() = score
}
object Repl extends App {
println(SingletonObject.name) // warabimochi
SingletonObject.name = "warabimochi2"
println(SingletonObject.name) // warabimochi2
println(SingletonObject.getScore()) // 100
println(SingletonObject.score) // Error
class ParentClass {
def printParentClassName = println("このクラスの名前はParentClassです。")
}
object SingletonObject2 extends ParentClass
SingletonObject2.printParentClassName // このクラスの名前はParentClassです。
object Temp {
def apply(name: String) = println(s"引数${name}でapplyメソッドが実行されました。")
}
Temp("warabimochi") // 引数warabimochiでapplyメソッドが実行されました。
class Dog(private val name: String) {
def printClassName = println(s"引数:${name} Dogクラスがprintされました。")
}
object Dog {
def apply(name: String) = new Dog("name")
def apply(num: Int) = new Dog(s"${num}番目の犬")
def printName(dog: Dog) = println(dog.name)
}
val dogStr = Dog("犬")
dogStr.printClassName // 引数:name Dogクラスがprintされました。
val dogNum = Dog(1)
dogNum.printClassName // 引数:1番目の犬 Dogクラスがprintされました。
Dog.printName(dogStr) // name
println(dogStr.name) // Error
}
case class
case class: データ構造を表現するのに使われるクラスです。
一部のフィールドを更新したい時はcopy
メソッドを使用する
case class名(引数1, 引数2...)
= case class
のインスタンス で分割代入が可能
case class
同士の比較はフィールドの値が全て等しいかどうかをチェックするため、
class
とは同値性に差が存在する
以下がcase class
の機能です。
-
toString
メソッドの実装 -
コンパニオンオブジェクト
・ファクトリメソッド
の実装 -
コンストラクタ引数に
val
を与える - 構造的等価性の実装
object Repl extends App {
case class CaseClassPoint(x: Int, y: Int)
var caseClassPoint1 = CaseClassPoint(1, 2) // CaseClassPoint(1,2)
println(caseClassPoint1.x) // 1
println(caseClassPoint1.y) // 2
var caseClassPoint2 = caseClassPoint1.copy(x = 3)
println(caseClassPoint2.x) // 3
println(caseClassPoint2.y) // 2
val CaseClassPoint(x, y) = caseClassPoint2
println(x) // 3
println(y) // 2
class ClassPoint(x: Int, y: Int) {}
val classPoint1 = new ClassPoint(1, 2)
val classPoint2 = new ClassPoint(1, 2)
println(s"同インスタンスの比較: ${classPoint1 == classPoint1}") // true
println(s"同フィールド異インスタンスの比較: ${classPoint1 == classPoint2}") // false
case class CaseClassPoint(x: Int, y: Int) {}
val caseClassPoint1 = CaseClassPoint(1, 2)
val caseClassPoint2 = CaseClassPoint(1, 2)
println(s"同インスタンスの比較: ${caseClassPoint1 == caseClassPoint1}") // true
println(s"同フィールド異インスタンスの比較: ${caseClassPoint1 == caseClassPoint2}") // true
}
sealed class
sealed
をクラス宣言の前に付けると同ファイルでのみ継承が可能になる
object Repl extends App {
sealed class SealedClass() {}
class Class() {}
class SealedClass2 extends SealedClass() {} // OK
class Class2 extends Class() {} // OK
}
import Repl.{Class, SealedClass}
object Temp {
class SealedClass2 extends SealedClass() {} // Error
class Class2 extends Class() {} // OK
}
case object + sealed classで実現する漏れないパターンマッチ
以下の実装例ではcase Female
が無いという指摘をコンパイラがしてくれるらしいが上手くいかない
enum
っぽく使うらしい
sealed class
でcase object
達をまとめている感覚でいるとよさそう
object Repl extends App {
sealed abstract class HumanGender
case object Male extends HumanGender
case object Female extends HumanGender
def printGender(gender: HumanGender) = gender match {
case Male => 1
}
}
package
package: 論理的に同一ファイルとして扱う仕組み
-> 同一packageなら別ファイルでもimport
が必要ない
package名は**ディレクトリ名・ディレクトリ階層と一致させる
ex)src/main/scala/temp/test.scala
-> package temp
逆ドメイン方式:package
の命名規則
ex)package jp.co.warabimochi.game
デフォルトパッケージ: package宣言が無い時に所属するパッケージのこと
デフォルトパッケージ同士は同パッケージと同じ扱いになる
パッケージオブジェクト: パッケージのトップレベルに存在する物を定義出来るオブジェクト
オブジェクトの中身は同一パッケージであればそのまま参照できる
-> 定義方法: パッケージ名とオブジェクト名を同一にする
package test
object testRepl extends App {
testRepl2.printMyObjectName() // 私のオブジェクト名はtestRepl2です。
printMyPackageObjectName() // 私はパッケージオブジェクトのtestです。
}
package test
object testRepl2 extends App {
def printMyObjectName() = println("私のオブジェクト名はtestRepl2です。")
printMyPackageObjectName() // 私はパッケージオブジェクトのtestです。
}
package object test {
def printMyPackageObjectName() = println("私はパッケージオブジェクトのtestです。")
}
import: package
を跨いでクラス・オブジェクト・メソッドを読み込む時に使う
単一のimport: import パッケージ名.クラス名
複数のimport: import パッケージ名.{クラス名1, クラス名2...}
package内全import: import パッケージ名._
package test2
import test.testRepl2
object Repl extends App {
testRepl2.printMyObjectName() // 私のオブジェクト名はtestRepl2です。
}
trait
トレイト: クラス
に対して動作をくっつけて増やすもの
ミックスイン: トレイトを継承すること
-
トレイトのミックスインには個数制限がない
1つ目の継承 or ミックスインはextends
を使うが、2つ目以降はwith
で繋げる -
トレイトは直接インスタンス生成できない
匿名内部クラス:new トレイト名 {}
でトレイトをミックスインした別のクラスを作ること
これを使うとトレイトのインスタンス生成が可能 -
トレイトにはコンストラクタ引数がない
trait Drink {
def drink(): Unit = println("飲む")
}
trait Eat {
def eat(): Unit = println("食べる")
}
class Repl
class RealRepl extends Repl with Drink with Eat
object Main extends App {
val realRepl = new RealRepl
realRepl.drink() // 飲む
realRepl.eat() // 食べる
val drink = new Drink {}
drink.drink() // 飲む
}
菱形継承問題: 複数ミックスインしたトレイト同士が同名のメソッドを持っていた場合
子クラスでそのメソッドを実行する際にどちらを実行すべきか分からないこと
- 解決策①:子クラスでメソッドを
override
する
trait Drink {
def drink(): Unit = println("飲む")
def eat(): Unit = println("ドリンクtraitだけど食べる")
}
trait Eat {
def eat(): Unit = println("食べる")
}
class Repl
class RealRepl extends Repl with Drink with Eat
class RealRepl2 extends Repl with Drink with Eat {
override def eat(): Unit = println("RealRepl2クラスでeat関数を呼ぶ")
}
object Main extends App {
val realRepl = new RealRepl
realRepl.eat() // Error
val realRepl2 = new RealRepl2
realRepl2.eat() // RealRepl2クラスでeat関数を呼ぶ
}
- 解決策②:親トレイトの抽象メソッドを子トレイトのメソッドで
override
させて線形化で解決する
線形化: トレイトの継承が直列で行われることで、override
が重なって解決されること
trait ParentTrait {
def callMyTraitName(): Unit
}
trait Trait1 extends ParentTrait {
override def callMyTraitName(): Unit = println("私のトレイト名はTrait1です。")
}
trait Trait2 extends ParentTrait {
override def callMyTraitName(): Unit = println("私のトレイト名はTrait2です。")
}
class ClassA extends Trait1 with Trait2
class ClassB extends Trait2 with Trait1
object Main extends App {
val instance1 = new ClassB // Trait1
val instance2 = new ClassA // Trait2
instance1.callMyTraitName() // 私のトレイト名はTrait1です。
instance2.callMyTraitName() // 私のトレイト名はTrait2です。
}
単一の多段継承時、super
は親トレイトを指す
trait ParentTrait {
def printMyTraitName(): Unit = println("ParentTrait: hoge")
}
trait TraitA extends ParentTrait {
override def printMyTraitName(): Unit = {
super.printMyTraitName()
println("hoge")
}
}
class Class1 extends TraitA
object Main extends App {
val instance = new Class1
instance.printMyTraitName()
// ParentTrait: hoge
// hoge
}
親トレイトを複数の子トレイトが継承する時、super
は親トレイトを指す
trait ParentTrait {
def hoge(): Unit = println("ParentTrait: hoge")
}
trait TraitA extends ParentTrait {
override def hoge(): Unit = {
super.hoge()
println("hoge")
}
}
trait TraitB extends ParentTrait {
override def hoge(): Unit = {
super.hoge()
println("hogehoge")
}
}
class Class1 extends TraitA with TraitB
object Main extends App {
val instance = new Class1
instance.hoge()
// ParentTrait: hoge
// hoge
// hogehoge
}