読者です 読者をやめる 読者になる 読者になる

新人SEの学習記録

14年度入社SEの学習記録用に始めたブログです。気づけば社会人3年目に突入。

学習記録:ドワンゴ 新人向けScala研修テキスト

学習記録 関数型 プログラミング Scala

参考文献

dwango.github.io

7. クラス

クラス定義

class クラス名 (コンストラクタ引数: コンストラクタ引数の型, ... ) {
  0個以上のフィールド定義またはメソッド定義
}

例えば,点を表すクラスPointは以下のように定義できる。

class Point(_x: Int, _y: Int) {
  val x = _x
  val y = _y
}

コンストラクタ引数をそのまま公開する場合,valを付けて以下のように書ける。

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

クラス名の直後にコンストラクタ定義があること,val/varでコンストラクタ引数をフィールドとして公開することができる点に注目。
Scalaでは1クラスにつき1つのコンストラクタしか使わない(文法上は複数定義できる)。

コンストラクタの引数にval/varをつけるとそのフィールドは公開され,外部からアクセスできるようになる。また,コンストラクタ引数のスコープはクラス定義全体に及ぶ。

メソッド定義

一般的には以下の形をとる。

(privete[this]/protected[package名]) def メソッド名(引数名: 引数の型, ... ): 返り値の型 = {
  本体のコード
}

privateをつけるとそのクラス内だけから,protectedをつけるとそのクラスの派生クラスからのみアクセスできるフィールドになる。
更に,private[this]をつけると同じオブジェクトからのみ,protected[パッケージ名]をつけると追加で同じパッケージに所属しているもの全てからアクセスできるようになる。
privateもprotectedも付けない場合,そのフィールドはpublicとみなされる。

scala> class Point(val x: Int, val y: Int) {
     |   def + (p: Point): Point = {
     |     new Point(x + p.x, y + p.y)
     |   }
     |   override def toString(): String = "(" + x + ", " + y + ")"
     | }
defined class Point

scala> val p1 = new Point(1, 1)
p1: Point = (1, 1)

scala> val p2 = new Point(2, 3)
p2: Point = (2, 3)

scala> p1 + p2
res23: Point = (3, 4)

メソッドのカリー化

メソッド複数の引数リストを持つことができる。

def メソッド名(引数名: 型名, ...)(引数名: 型名, ...): 返り値の型 = 本体

上記のように複数の引数リストを持つように定義したとき,メソッド定義はカリー化されていると言う。
カリー化された加算メソッドを定義してみる。

scala> class Adder {
     |   def add(x: Int)(y: Int): Int = x + y
     | }
defined class Adder

scala> val adder = new Adder()
adder: Adder = Adder@54aea9d6

scala> adder.add(2)(3)
res24: Int = 5

scala> adder.add(2)_
res25: Int => Int = <function1>

scala> res25(4)
res26: Int = 6

カリー化されたメソッドはobj.m(x)(y)の形式で呼び出すことになる。
また,一番下の例のように最初の引数だけを適用して,新しい関数を作る(部分適用)こともできる。

フィールド定義

(private/protected) (val/var) フィールド名: フィールドの型 = フィールドに代入される値の式

継承

継承には二つの目的がある。
一つは継承によりスーパークラスの実装をサブクラスでも使うことで実装を再利用すること。
もう一つは複数のサブクラスが共通のスーパークラスのインタフェースを継承することで処理を共通化すること。

実装の継承には,複数の継承によりメソッドやフィールドの名前が衝突する場合の振る舞いなどに問題があり,Javaでは実装継承が一つだけに限定されている。
Scalaではトレイトという仕組みで複数の実装の継承を実現している(トレイトについては別の節で説明)。

ここでは,通常のScalaのクラスの継承について説明する。

class (...) extends 継承元クラス {
  ...
}

基本的に,継承の働きはJavaのクラスと同じだが,既存のメソッドをオーバーライドするときはoverrideキーワードを使わなければならない点が異なる。

scala> class APrinter() {
     |    def print(): Unit = {
     |      println("A")
     |    }
     | }
defined class APrinter

scala> class BPrinter() extends APrinter {
     |    override def print(): Unit = {
     |      println("B")
     |    }
     | }
defined class BPrinter

scala> new APrinter().print
A

scala> new BPrinter().print
B

ここでoverrideキーワードを外すと,メッセージを出力してコンパイルエラーになる。