0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【備忘録】Scala入門

Posted at

前提

Scala2系です。
3系だとパッケージオブジェクトとかtraitのwithとか違うと思います

暗黙の了解

Scalaのファイル名はアッパーキャメルケースを使用します。(Main.Scala・Index.Scala・・・)

オブジェクト

オブジェクト:状態・処理を保持できる機構

構文

Main.Scala
// Mainというオブジェクトを作成する
object Main

トレイト

トレイト:振る舞いを定義するもの
ミックスイン:トレイトで定義した振る舞いをオブジェクトに追加すること

Main.Scala
// 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句にSeqArray等を渡すと要素を束縛して取り出せる
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

クラス: インスタンスを作る雛形
インスタンス: クラスから作られたオブジェクト

  • フィールド: クラスが持つ状態
  • メソッド: クラスが持つ振る舞い
  • メンバー: フィールドメソッドを合わせた呼び名

コンストラクタ: オブジェクトの生成と初期化を同時に行う時に利用するメソッド
コンストラクタ引数にvalvarを付けることで外部にフィールドを公開する
カプセル化: メソッドに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つしか存在しないオブジェクト

objectclassを継承することで、newを使わなくても、インスタンス生成が可能

objectapplyメソッドは object名(引数) で呼び出せる
なので、コンパニオンオブジェクトを作成し、
applyメソッドにファクトリメソッドを作ることが多い
コンパニオンオブジェクト: class名と同名のobject
ファクトリメソッド: インスタンスを生成する戻り値として返すメソッド

コンパニオンオブジェクトの中からなら同名classprivateなメンバーを参照できる

以下のケースで使う

  • グローバル変数ユーティリティーメソッド
  • 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をクラス宣言の前に付けると同ファイルでのみ継承が可能になる

Repl.scala
object Repl extends App {
  sealed class SealedClass() {}
  class Class() {}

  class SealedClass2 extends SealedClass() {}  // OK
  class Class2 extends Class() {}  // OK
}
scala Temp.scala
import Repl.{Class, SealedClass}

object Temp {
  class SealedClass2 extends SealedClass() {}  // Error
  class Class2 extends Class() {}  // OK
}

case object + sealed classで実現する漏れないパターンマッチ

以下の実装例ではcase Femaleが無いという指摘をコンパイラがしてくれるらしいが上手くいかない

enumっぽく使うらしい
sealed classcase 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宣言が無い時に所属するパッケージのこと
デフォルトパッケージ同士は同パッケージと同じ扱いになる

パッケージオブジェクト: パッケージのトップレベルに存在する物を定義出来るオブジェクト
オブジェクトの中身は同一パッケージであればそのまま参照できる
-> 定義方法: パッケージ名オブジェクト名を同一にする

src/main/scala/test/testRepl.scala
package test

object testRepl extends App {
  testRepl2.printMyObjectName()  // 私のオブジェクト名はtestRepl2です。
  printMyPackageObjectName()  // 私はパッケージオブジェクトのtestです。
}
src/main/scala/test/testRepl2.scala
package test

object testRepl2 extends App {
  def printMyObjectName() = println("私のオブジェクト名はtestRepl2です。")
  printMyPackageObjectName()  // 私はパッケージオブジェクトのtestです。
}
src/main/scala/test/package.scala
package object test {
  def printMyPackageObjectName() = println("私はパッケージオブジェクトのtestです。")
}

import: packageを跨いでクラス・オブジェクト・メソッドを読み込む時に使う
単一のimport: import パッケージ名.クラス名
複数のimport: import パッケージ名.{クラス名1, クラス名2...}
package内全import: import パッケージ名._

src/main/scala/test2/Repl.scala
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です。
}

image.png

単一の多段継承時、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
}

image.png

親トレイトを複数の子トレイトが継承する時、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
}

image.png

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?