クラス

これからクラスとは何かということに関して説明してます。最低限、Javaのクラスがわかっている事を前提にしますが、ご了承ください。

クラス定義

Scalaにおけるクラスは、記法を除けばJava言語のクラスと大して変わりません。Scalaのクラス定義はおおまかには次のような形を取ります。

class ClassName(parameter1: Type1, parameter2: Type2, ...) {
  (a field or method definition)の0回以上の繰り返し
}

たとえば、点を表すクラスPointを定義したいとします。Pointはx座標を表すフィールドxInt型)とフィールドyInt型)からなるとします。このクラスPointをScalaで書くと次のようになります。

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

コンストラクタの引数をそのまま公開したい場合は、以下のように短く書くこともできます。

class Point(val x: Int, val y: Int)
  • クラス名の直後にコンストラクタの定義がある
  • val/varによって、コンストラクタ引数をフィールドとして公開することができる

点に注目してください。まず、最初の点ですが、Scalaでは1クラスに付き、基本的には1つのコンストラクタしか使いません。文法上は複数のコンストラクタを定義できるようになっていますが、実際に使うことはまずないので覚える必要はないでしょう。一応、Scalaでは複数のコンストラクタが定義できるので、この最初の1つのコンストラクタをプライマリコンストラクタとして特別に扱っています。

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

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 + ")"
}

のように、メソッド定義の中から直接コンストラクタ引数を参照できます。

メソッド定義

先ほど既にメソッド定義の例として+メソッドの定義が出てきましたが、一般的には、

(private[this/package名]/protected[package名]) def methodName(parameter1: Type1, parameter2: Type2, ...): ReturnType = {
  ???
}

という形をとります。ちなみに、メソッド本体が1つの式だけからなる場合は、

(private[this/package名]/protected[package名]) def methodName(parameter1: Type1, parameter2: Type2, ...): ReturnType = ???

と書けます(実際には、こちらの方が基本形で、= {}を使ったスタイルの方が{}内に複数の式を並べて書けることを利用した派生形になりますが、前者のパターンを使うことが多いでしょう)。

返り値の型は省略しても特別な場合以外型推論してくれますが、読みやすさのために、返り値の型は明記する習慣を付けるようにしましょう。privateを付けるとそのクラス内だけから、 protectedを付けると派生クラスからのみアクセスできるメソッドになります。 private[this] をつけると、同じオブジェクトからのみアクセス可能になります。また、 private[package名]を付けると同一パッケージに所属しているものからのみ、 protected[package名] をつけると、派生クラスに加えて追加で同じパッケージに所属しているもの全てからアクセスできるようになります。privateprotectedも付けない場合、そのメソッドはpublicとみなされます。

先ほど定義したPointクラスをREPLから使ってみましょう。

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, 2)
p2: Point = (2, 2)

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

複数の引数リストを持つメソッド

メソッドは以下のように複数の引数リストを持つように定義することができます。

def methodName(parameter11: Type11, parameter12: Type12, ...)(...)(parameterN1: TypeN1, ..., parameterNM: TypeNM): RerurnType = ???

複数の引数リストを持つメソッドには、Scalaの糖衣構文と組み合わせて流暢なAPIを作ったり、後述するimplicit parameterのために必要になったり、型推論を補助するために使われたりといった用途があります。何はともあれ、複数の引数リストを持つ加算メソッドを定義してみましょう。

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

scala> val adder = new Adder()
adder: Adder = Adder@497c0466

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

scala> adder.add(2) _
res2: Int => Int = $$Lambda$6612/1391352698@52c26707

複数の引数リストを持つメソッドはobj.m(x, y)の形式でなくobj.m(x)(y)の形式で呼びだすことになります。また、一番下の例のように最初の引数だけを適用して新しい関数を作る(部分適用)こともできます。

フィールド定義

フィールド定義は

(private[this/package名]/protected[package名]) (val/var) fieldName: Type = Expression

という形を取ります。valの場合は変更不能、varの場合は変更可能なフィールドになります。また、privateを付けるとそのクラス内だけから、protectedを付けるとそのクラスの派生クラスからのみアクセスできるフィールドになります。 private[this] を付加すると、同じオブジェクトからのみアクセス可能になります。さらに、private[package名]を付けると同一パッケージからのみ、 protected[package名] をつけると、派生クラスに加えて同じパッケージに所属しているもの全てからアクセスできるようになります。 privateprotectedも付けない場合、そのフィールドはpublicとみなされます。

private[this]を付けたフィールドへのアクセスは一般にJVMレベルでのフィールドへの直接アクセスになるため、若干高速です。細かいレベルでのパフォーマンスチューニングをする際は意識すると良いでしょう。

先ほど定義したPointクラスをREPLから使ってみましょう。

継承

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

実装の継承には複数の継承によりメソッドやフィールドの名前が衝突する場合の振舞いなどに問題があることが知られており、Javaでは実装継承が1つだけに限定されています。 Java 8ではインタフェースにデフォルトの実装を持たせられるようになりましたが、変数は持たせられないなどの制約があります。 Scalaではトレイトという仕組みで複数の実装の継承を実現していますが、トレイトについては別の節で説明します。

ここでは通常のScalaのクラスの継承について説明します。 Scalaでのクラスの継承は次のような構文になります。

class SubClass(....) extends SuperClass {
  ....
}

基本的に、継承のはたらきは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キーワードをはずすと、

scala> class BPrinter() extends APrinter {
     |   def print(): Unit = {
     |     println("B")
     |   }
     | }
<console>:14: error: overriding method print in class APrinter of type ()Unit;
 method print needs `override' modifier
         def print(): Unit = {
             ^

のようにメッセージを出力して、コンパイルエラーになります。Javaではしばしば、気付かずに既存のメソッドをオーバーライドするつもりで新しいメソッドを定義してしまうというミスがありましたが、Scalaではoverrideキーワードを使って言語レベルでこの問題に対処しているのです。

1. このように継承などにより型に親子関係を作り、複数の型に共通のインタフェースを持たせることをサブタイピング・ポリモーフィズムと呼びます。Scalaでは他にも構造的部分型というサブタイピング・ポリモーフィズムの機能がありますが、実際に使われることが少ないため、このテキストでは説明を省略しています。

results matching ""

    No results matching ""