S-JIS[2010-10-23/2017-01-21] 変更履歴

Scalaクラス

Scalaのクラスのメモ。


class

(Javaと同様の)複数インスタンス作るクラスは、classで定義する。
(staticメソッドに当たるものが欲しい場合はobjectで定義する)

class クラス名 {
}

→アクセス修飾子(protected・private)
→ジェネリクス(class クラス名[A])


他のクラスから派生する(他のクラスを継承する)場合、extendsを使う。

class クラス名 extends 他クラス {
}

親クラスの引数つきコンストラクターを呼び出したい場合は、親クラス名の後ろに引数を書く。[2010-12-21]

class クラス名 extends 親クラス(値, …) {
}

トレイト(Javaのインターフェースのようなもの)を継承する場合、withを使う。ただしextendsが無い場合、最初のトレイトだけはextendsで指定する。
複数のトレイトを継承する場合は「with トレイト」を追加していく。

class クラス名 extends 親クラス with トレイト
class クラス名 extends 親クラス with トレイト1 with トレイト2
class クラス名 extends トレイト
class クラス名 extends トレイト1 with トレイト2

→トレイトの継承方法・初期化順序


抽象クラス(メンバーの具体的な定義をせず、子クラスで定義する)の場合は、abstractを付ける。
(Javaのabstractクラスと同様)

abstract class クラス名 extends 〜 {
}

→抽象メンバー


自クラスからそれ以上派生できない(自クラスを継承できない)ようにする場合は、finalを付ける。
(Javaのfinalクラスと同様)

final class クラス名 extends 〜 {
}

→メンバーに付けるfinal

finalでなくsealedを使うと、同一ソースファイル内のクラスは継承できるが、別ファイルのクラスからは継承できなくなる。[2010-12-21]

sealed class クラス名 extends 〜 {
}

class 同一ファイル内のクラス extends クラス名 {	//OK
}
class 別ファイルのクラス extends クラス名 {	//コンパイルエラー
}

クラス本体に書くものが何も無い場合は、波括弧(のブロック)を省略できる。[2012-05-19]

class クラス名
class クラス名(引数定義)
class クラス名 extends 別クラス
class クラス名 extends 別クラス(引数)

メンバー(フィールド・メソッド)

classやobjectのブロックの中にフィールド(変数・定数)やメソッドの定義を書く。[2010-12-19]

class クラス名 {
  type 型名 = 型

  val 定数名 = 値

  var 変数名 = 初期値
  var 変数名:型 = _	//初期値をその型のデフォルト値とする

  def メソッド名(引数) = 本体
}

フィールド名とメソッド名には同じものを付けることは出来ない。
(Javaではメソッド呼び出しなら「()」を付けるし、それが無ければフィールドアクセスとして区別できる。
 Scalaではメソッド名のみを指定できる場面があるので、その場合にフィールドと区別できない)

→アクセス修飾子(protected・private)


変数・定数・メソッド等の宣言(val・var・def・type)において「= 〜」を書かなかった場合、抽象フィールド・抽象メソッドを定義したことになる。
つまり、そのクラスを継承した子クラス(具象クラス)で実際に値(本体)を定義する必要がある。
(Javaでは、フィールドは抽象化することは出来ない。また、抽象メソッドを定義する際はメソッドの前にabstractを付ける必要があるが、Scalaでは付けない)

abstract class 抽象クラス名 {
  type 型名

  val 定数名:型

  var 変数名:型

  def メソッド名(引数):戻り型
  def メソッド名(引数)		//戻り値の型を指定しないと、Unitになるようだ
}

class 具象クラス extends 抽象クラス名 {
  type 型名 = 型

  val 定数名 = 値

  var 変数名 = 値

  def メソッド名(引数) = 本体
}

抽象メソッドでないメソッド(処理本体が書かれているメソッド)をオーバーライドする際は、メソッド定義の前にoverrideを付ける必要がある。
(Java(JDK1.5以降)では@Overrideアノテーションを付けるが、Scalaでは文法に含まれている)

class クラス名 extends 親クラス {
  override def メソッド名(引数) = 本体
}

抽象メソッドの処理本体を定義する場合は、overrideを付けても付けなくてもよい。(個人的には付けた方が分かりやすいと思うが、Scalaの文化としては付けない方が優勢っぽい)

→共変戻り値型
→defによって定義されたメソッドをvalでオーバーライド


メンバー(フィールド・メソッド)を子クラスでオーバーライドできなくしようと思ったら、finalを付ける。
(Javaのfinalメソッドと同様)

class クラス名 {
  final val 定数名 = 値

  final var 変数名 = 初期値
  
  final def メソッド名(引数) = 本体
}

親クラスのメソッドにアクセスしたい場合はsuperを使う(Javaのsuperと同様)。[2010-12-25]
自クラスのフィールド・メソッドを明示したい場合はthisを使う(Javaのthisと同様)。

class A {
  val V = 123
  def f() = println("aaa")
}

class B extends A {
  override def f() = {
    super.f()

    val V = 456
    println(V)
    println(this.V)
  }
}
scala> new B().f()
aaa
456
123

→super[トレイト名]


コンストラクター

Scalaでは、classのブロックの中に直接処理を書くのがコンストラクターになる。[2010-12-19]

Scala Java相当 備考
class クラス名 {


  //ここに初期処理を記述

}
public class クラス名 {
  /** コンストラクター */
  public クラス名() {
    //ここに初期処理を記述
  }
}
Scalaでクラスのブロック内に書いたコンストラクターを「基本コンストラクター(primary constructor)」と呼ぶ。
Javaでは、クラス名と同名のメソッド定義(のようなもの)がコンストラクター。
class Example {
  var v1 = 123
  printf("A %d %d%n", v1, v2)
  var v2 = 456
  printf("B %d %d%n", v1, v2)
}
public class Example {
  public int v1 = 123;
  { System.out.printf("A %d %d%n", v1, v2); }
  public int v2 = 456;
  { System.out.printf("B %d %d%n", v1, v2); }
}
public class Example {
  public int v1;
  public int v2;

  /** コンストラクター */
  public Example() {
    v1 = 123;
    System.out.printf("A %d %d%n", v1, v2);
    v2 = 456;
    System.out.printf("B %d %d%n", v1, v2);
  }
}
Scalaのコンストラクターは、メソッド・関数内の処理を記述するのと似ている。
関数はdefの後のブロックに処理を記述するが、classのブロックに処理を記述するのがコンストラクター。
インスタンス生成時に、ブロック内を上から順に処理していく。
varやval・defによる定義がクラスのフィールド・メソッドになる。
上から順に処理が実行されていくので、そこより下で定義される変数には とりあえずその型のデフォルト値が入る。
Scalaのコンストラクターは、Javaでの初期化子を使った記述に似ている。
(左記の上の例では、1つ目のprintf時点ではv2の定義がまだ現れていないので、コンパイルエラーになるが)

実際は、左記の下の例のように、変数宣言だけ先にあり、コンストラクターの中に全ての記述が入る。
class クラス名(引数名:型, …) {


  //ここに初期処理を記述

}
class Example(a0:Int, a1:Int) {
  val n0 = a0
  var n1 = a1
}
public class クラス名 {
  /** コンストラクター */
  public クラス名(型 引数名, …) {
    //ここに初期処理を記述
  }
}
public class Example {
  public final int n0;
  public int n1;

  /** コンストラクター */
  public Example(int a0, int a1) {
    this.n0 = a0;
    this.n1 = a1;
  }
}
基本コンストラクターに引数を持たせたい場合、Scalaではクラス名の後ろに引数を書く。
class Example(
  val n0: Int,
  var n1: Int
) {
}
public class Example {
  public final int n0;
  public int n1;

  /** コンストラクター */
  public Example(int a0, int a1) {
    this.n0 = a0;
    this.n1 = a1;
  }
}
基本コンストラクターの引数をただ単にフィールドの初期値としたいだけの場合、
コンストラクターの引数にval・varを付けると、それがフィールドになる。
class Example(a0:Int, a1:Int) {
  val n0 = a0

  def foo() = a1
}
public class Example {
  public  final int n0;
  private final int a1;
  public int foo() = { return a1; }

  /** コンストラクター */
  public Example(int a0, int a1) {
    this.n0 = a0;
    this.a1 = a1;
  }
}
基本コンストラクターの引数にval・varを付けなくても、メソッド内から使用できる。[2011-01-04]
この場合、暗黙にprivateな不変フィールド(val)が定義されて、値が保持される。
class Example
class Example(val n0:Int)
  クラスの本体に何も書くことが無い場合、とげ括弧も省略できる。
class Example(a0:Int, a1:Int) {
  val n0 = a0
  var n1 = a1







  //コンストラクター
  def this(a:Int) = {
    this(a, 2)
    println("サブ")
  }
}
class Example(
  val n0: Int,
  var n1: Int
) {






  //コンストラクター
  def this(a: Int) = this(a, 2)
  def this()       = this(1)
}
public class Example {
  public final int n0;
  public int n1;

  /** コンストラクター */
  public Example(int a0, int a1) {
    this.n0 = a0;
    this.n1 = a1;
  }

  //コンストラクター
  public Example(int a) {
    this(a, 2);
    System.out.println("サブ");
  }
}
public class Example {
  public final int n0;
  public int n1;

  /** コンストラクター */
  public Example(int a0, int a1) {
    this.n0 = a0;
    this.n1 = a1;
  }

  //コンストラクター
  public Example(int a) { this(a, 2); }
  public Example()      { this(1);    }
}
引数の異なる複数のコンストラクターを用意(オーバーロード)したい場合、thisという名前のメソッドを定義するとコンストラクターになる。
これを「補助コンストラクター(auxiliary constructor)」と呼ぶ。
補助コンストラクターの先頭では必ずthis(〜)という呼び出しを行い、自分より前に定義されたコンストラクターを呼び出す必要がある。[/2012-05-19]
class Example(
  val n0: Int = 1,
  var n1: Int = 2
)
  Scala2.8では、メソッド定義と同様に、引数のデフォルト値を指定することが出来る。
class A(a:Int) {

}

class B extends A(123) {



}

class C(n:Int) extends A(n) {



}
public class A {
  public A(int a) {}
}

public class B extends A {
  public B() {
    super(123);
  }
}

public class C extends A {
  public C(int n) {
    super(n);
  }
}
親クラスの引数付きコンストラクターを呼び出したい場合は、extendsで指定したクラス名の後ろに引数を書く。[2010-12-21]

コンストラクターの定義順序

コンストラクターを複数定義する(補助コンストラクターを定義する)場合、定義順序に意味があるので気をつける必要がある。[2015-03-07]
Scalaでは、補助コンストラクターは自分より前に定義されたコンストラクターを必ず呼ぶ必要がある。(最終的には基本コンストラクターが必ず呼ばれることになる)

(Javaの場合は、コンストラクターを定義する順序は自由。また、「別のコンストラクターを呼ばなければならない」といったルールは無い。(親クラスのコンストラクターのいずれかは必ず呼ばれる、というルールはあるが))

例
class Example1(n1: Int, n2: Int, n3: Int) {

  def this(n1: Int, n2: Int) = {
    // 基本コンストラクター呼び出し
    this(n1, n2, 3)
  }

  def this(n1: Int) = {
    // 前で定義されているコンストラクター呼び出し
    this(n1, 2)
  }

  def this() = {
    // 前で定義されているコンストラクター呼び出し
    this(1)
  }
}
class Example2(n1: Int, n2: Int, n3: Int) {

  def this() = {
    // 後で定義されているコンストラクターの呼び出し
    this(1)
  }

  def this(n1: Int) = {
    // 後で定義されているコンストラクターの呼び出し
    this(n1, 2)
  }

  def this(n1: Int, n2: Int) = {
    this(n1, n2, 3) // 基本コンストラクター呼び出し
  }
}
class Example3(n1: Int, n2: Int, n3: Int) {

  def this() = {
   // 基本コンストラクター呼び出し
    this(1, 2, 3)
  }

  def this(n1: Int) = {
    // 基本コンストラクター呼び出し
    this(n1, 2, 3)
  }

  def this(n1: Int, n2: Int) = {
    // 基本コンストラクター呼び出し
    this(n1, n2, 3)
  }
}
OK constructor2.scala:5: error: called constructor's definition must precede calling constructor's definition
    this(1)
    ^
constructor2.scala:10: error: called constructor's definition must precede calling constructor's definition
    this(n1, 2)
    ^
OK

自分型アノテーション

基本コンストラクターの先頭で、自分自身のインスタンスを指す識別子(変数のようなもの)を定義することが出来る。[2011-01-08]
(self type annoationsの構文のうち、型の指定が無い形式。self aliasesとでも言うべきもの[2012-04-29])
識別子は何でもよいが、慣習としてselfを使うらしい。

class クラス名 {
  識別子 =>

  〜
}

ただ、自分自身を指すキーワードとしては「this」があるわけで、この識別子の値とthisの値は同じになる。(つまりthisの別名を定義することになる)

Scala Java類似 備考
class C {
  self =>

  def print() = {
    println(self)
    println(this)
  }
}
scala> new C().print
line163$object$$iw$$iw$C@1c44fe9
line163$object$$iw$$iw$C@1c44fe9
class C {
  private final C self = this;

  public void print() {
    System.out.println(self);
    System.out.println(this);
  }
}
 
 
class C {
  ccc =>

  class D {
    ddd =>

    def print() = {
      println(ccc)
      println(C.this)
      println(ddd)
      println(this)
      println(D.this)
    }
  }

  def print() = { 
    new D().print
  }
}
scala> new C().print
line169$object$$iw$$iw$C@18dc649
line169$object$$iw$$iw$C@18dc649
line169$object$$iw$$iw$C$D@d5064b
line169$object$$iw$$iw$C$D@d5064b
line169$object$$iw$$iw$C$D@d5064b
class C {
  private final C ccc = this;

  class D {
    private final D ddd = this;

    void print() {
      System.out.println(ccc);
      System.out.println(C.this);
      System.out.println(ddd);
      System.out.println(this);
      System.out.println(D.this);
    }
  }

  public void print() {
    new D().print();
  }
}
Javaでは、内部クラスから外側クラスのインスタンスを取得する為に
「外側クラス.this」という指定が出来る。
自分自身のインスタンスも「自クラス名.this」という書き方が出来る。
Scalaでも同じ指定方法が可能。
(superを使った書き方は出来なさそう)

“あるクラスのインスタンスを示している”ことをはっきりさせる為に、「クラス名.this」という構文を使うのでなく、「識別子 =>」で名前を付けてそれを使う方が良い、ということだろうか。
※無名クラスの場合は「クラス名.this」という書き方が出来ないので、=>で別名を付けられるのは便利。[2011-04-03]


この構文では型を指定することができ、それを自分型アノテーション(Self-Type Annotations)と呼ぶ。[2011-04-03/2012-04-29]
特に自分型アノテーションが威力を発揮するのは、識別子に(自分以外の)別の型を指定できること。[2011-08-25]

class クラス名 {
  識別子: 型 =>

  〜
}
Scala Java
類似
備考
trait A {
  def a() = println("A")
}

class C {
  self: A =>

  def c() = println("C")

  def print() = {
    a()
    c()
  }
}
scala> val c = new C with A
c: C with A = $anon$1@1c2ee8e

scala> c.print
A
C
  自分型アノテーションでは自分以外の別の型(クラス・トレイト)を宣言することが出来る。[2011-04-03]
こうすると、そのクラスも自分自身が継承している扱いとなり、そのクラスのメソッドを呼べるようになる。
なお、複数のトレイトを継承したい場合は「self: A with B =>」 の様にwithでつなぐ。
ただし実際にインスタンスを作る際に、そのクラスもちゃんと継承(ミックスイン)する必要がある。
左記の例で「new C」のみだと「class C cannot be instantiated because it does not conform to its self-type C with A」というエラーになる。
自分型を使わずに「class C extends A」でも同じことは出来るが。
クラスを使う側がAそのものではなくAを拡張した別クラスを使いたい場合には自分型の方が便利。
また、「extends A」だとCの定義としてAが表に現れるが、自分型だと表面には出てこない。
scala> class A { def f() = "a" }
defined class A

scala> trait B extends A {
     |   def g() = super.f
     | }
defined trait B

scala> trait B {
     |   self: A =>
     |   def g() = super.f
     | }
  extendsした場合はsuperで親クラスのメソッドを呼び出せるが、自分型の場合は出来ない。[2011-04-06]
「value f is not a member of java.lang.Object with ScalaObject」 というコンパイルエラーになる。

→トレイトと自分型アノテーションを使ってソースファイル分割を行う例


アクセス修飾子

Scalaでは、アクセス修飾子を付けないとpublic(どこからでもアクセス可)になる。[2010-12-19]

アクセス修飾子 class クラス名 {
  アクセス修飾子 val 定数定義
  アクセス修飾子 var 変数定義
  アクセス修飾子 def メソッド定義
}
Scala Java相当 アクセス可能な範囲 備考
(デフォルト) public どこからでもアクセス可能。  
protected   自分(自クラス)と自分を継承した子クラスからアクセス可能。  
private private 自分(自クラス)からのみアクセス可能。  
protected[パッケージ名] protected 指定パッケージと自分を継承した子クラスからアクセス可能。 パッケージ名には、自分が属するパッケージだけ指定可能。
「ルートパッケージ.サブパッケージ」といった、ピリオド「.」を使った指定は出来ない。
private[パッケージ名] (デフォルト) 指定パッケージからアクセス可能。
protected[クラス名]   指定クラスと自分を継承した子クラスからアクセス可能。 自分を内包しているクラス(自分の外側のクラス)だけ指定可能。
「パッケージ.クラス名」といった、ピリオド「.」を使った指定は出来ない。
private[クラス名]   指定クラスからアクセス可能。
protected[this]   自分(自インスタンス)と自分を継承した子クラスからアクセス可能。 単なるprotected・privateは、自分と同じクラスの別インスタンスにアクセス可能だが、
thisを付けると同一クラスであっても別インスタンスにはアクセスできなくなる。
private[this]   自分(自インスタンス)からのみアクセス可能。

→パッケージによるアクセス可能範囲
↓クラス指定によるアクセス可能範囲

class C1 {

  // 自クラス(C1の内側)
  class C2 {
                    val c2pub  = 1
    protected       val c2pro  = 1
    private         val c2pri  = 1
    protected[this] val c2prot = 1
    private[this]   val c2prit = 1
    protected[C1]   val c2proc = 1
    private[C1]     val c2pric = 1
  }

  def c1method() = {
    val c2 = new C2()
    c2.c2pub
//x c2.c2pro
//x c2.c2pri
//x c2.c2prot
//x c2.c2prit
    c2.c2proc
    c2.c2pric
  }
}

アクセス修飾子の記述箇所の例。[2010-12-21]

例 備考
private class クラス {
}
クラス自体のアクセス制御。
class クラス {
  private val 定数 = 値
  private def メソッド() = 式
}
メンバー(フィールド・メソッド)のアクセス制御。
class クラス(
  private val 定数:型
) {
}
基本コンストラクターの引数でフィールドを定義する場合の、フィールドのアクセス制御。
class クラス private(引数:型) {
}
class クラス private() {
}
class クラス private {
}
基本コンストラクターのアクセス制御。
class クラス {
  private def this(引数:型) = {
    this()
  }
}
補助コンストラクターのアクセス制御(メソッドと同じ)。
基本コンストラクターをprivateにして補助コンストラクターをpublic(アクセス修飾子なし)にすることも問題ない。

インスタンス生成(new)

インスタンスは「new クラス名()」で生成する。
Scalaでは、引数の無いコンストラクターの場合は丸括弧が省略できるので、「new クラス名」でも可。

引数のあるコンストラクターを呼び出す場合は「new クラス名(引数,…)」となる。[2010-12-21]
なお、メソッドの場合は引数が1個だけのときは丸括弧「()」の代わりに波括弧「{}」を使うことが出来るが、newでは出来ない。

よくサンプルで見かける「Array(1,2,3)」「Array("a","b","c")」「List(1,2,3)」といったnewを使わない初期化方法は、newが省略されているわけではなく、apply()メソッドの呼び出しである。

「new クラス名〜」の後に波括弧「{}」を付けることで、元のクラスを拡張したインスタンスを生成することが出来る。[2011-01-09]
(Javaの無名内部クラス(匿名クラス)と同様)
それどころか、newの直後に波括弧を付けることで、新しいインスタンスを作ることが出来る。
(この場合はAnyRefを継承したクラスになる)

new クラス名
new クラス名(引数…)
new クラスまたはトレイト名         { メンバー定義 }
new クラスまたはトレイト名(引数…) { メンバー定義 }
new クラスまたはトレイト名         with トレイト1 with …
new クラスまたはトレイト名(引数…) with トレイト1 with …
new クラスまたはトレイト名         with トレイト1 with … { メンバー定義 }
new クラスまたはトレイト名(引数…) with トレイト1 with … { メンバー定義 }
new { メンバー定義 }
new { 事前初期化   }               with トレイト1 with … { メンバー定義 }

内部クラス

クラス定義の中でクラス(内部クラス)を定義することが出来る。[2011-01-09]
ただ、この内部クラスの扱いがJavaとScalaでは少々異なる。

Scala Java相当 備考
class A {
  class B {
  }
}
class A {
  class B {
  }
}
内部クラスの定義方法はScalaとJavaで同じ。
class A {
  class B {
  }

  def createB() = {
    new B()
  }
}
class A {
  class B {
  }

  public B createB() {
    return new B();
  }
}
外側クラスの中で内部クラスのインスタンスを生成する方法もScalaとJavaで同様。
val a = new A()
val b = new a.B()
× new (a).B()
× new (new A()).B()
A   a = new A();
A.B b = a.new B();
○ new (a).new B();
○ new A().new B();
外部から内部クラスのインスタンスを生成する構文は異なる。
scala> val a1 = new A()
a1: A = A@10ccdfa

scala> val a2 = new A()
a2: A = A@13f582a

scala> var b1 = new a1.B()
b1: a1.B = A$B@19ce161

scala> val b2 = new a2.B()
b2: a2.B = A$B@14089dc

scala> b1 = b2
<console>:21: error: type mismatch;
 found   : a2.B
 required: a1.B
       b1 = b2
            ^
scala> val a3 = a1
a3: A = A@10ccdfa

scala> b1 = new a3.B()
<console>:20: error: type mismatch;
 found   : a3.B
 required: a1.B
       b1 = new a3.B()
            ^
A a1 = new A();


A a2 = new A();


A.B b1 = a1.new B();


A.B b2 = a2.new B();


b1 = b2; //代入OK
同じ外側クラス(A)で別インスタンス(a1, a2)を作り、
それぞれが内部クラス(B)のインスタンス(b1, b2)を作ったとする。
Javaの場合はb1とb2の型(クラス)は同一だが、Scalaでは違うものとして識別される。

外側クラス(A)が同じインスタンスであるかどうかでもなく、
「どの変数(経路)を使って内部クラスがインスタンス化されたのか」
によって決まるようだ。
こういうのを「パス依存型(path-dependent types)」と言うらしい。
scala> var b: A#B = b1
b: A#B = A$B@19ce161

scala> b = b2
b: A#B = A$B@14089dc
scala> var b = b1.asInstanceOf[A#B]
b: A#B = A$B@19ce161
  パス依存で異なっている内部クラスを同じクラスとして扱うには、
「外側クラス名#内部クラス名」にキャストする。

参考: Astarisk Works WikiのScala for Java programmers

ケースクラス

Scalaのクラスには「こういった使い方をよくする」という事柄がある。[2011-01-10]
クラス定義時に「case」を付けてケースクラス(case class)にすると、便利なメソッドやコンパニオンオブジェクトが自動的に生成される。
また、java.io.Serializableもミックスインされる。[2014-09-15]

case class クラス名(引数…) {
}
クラスの自動生成内容 説明
with scala.Product Productトレイトがミックスインされる。[2017-01-21]
with scala.Serializable Serializableトレイトがミックスインされる。[2017-01-21]
このトレイトはJavaのSerializableと同等。つまりシリアライズ可能という意味。
valフィールド 基本コンストラクターの引数全てに「val」を付けた状態になる。
つまりフィールドが自動的に定義される。
(対象はあくまで基本コンストラクターであり、補助コンストラクターは無関係)
引数定義時に「var」を明示的に付けていれば、varになる。[2011-08-25]
equals() 各フィールドの内容を比較するメソッド。
hashCode() 各フィールドの内容を元にハッシュ値を算出するメソッド。
toString() 自分のクラス名と各フィールドの内容を出力するメソッド。
copy() 値をコピーして新しいインスタンスを生成するメソッド。(Scala2.8)
基本コンストラクターと同様の引数を受け取り、自分と同じクラスのインスタンスを返す。
引数は可変で、足りない部分は自分のインスタンスの値が使われる。
基本コンストラクターの変数名を使用した指定(名前指定引数)も可能。
productArity
productElement()
productIterator
productPrefix
Productトレイトの各メソッド。
例えばproductArityでコンストラクターの引数(フィールド)の個数、
productElement(n)・productIteratorで引数(フィールド)の値、
productPrefixでクラス名の文字列が取れる。
(フィールド名の文字列が定義順に取れればもっと便利なんだけどなー)
canEqual() 自分と同じクラスだったらtrueを返すメソッド。
(Product(の親トレイトのEquals)のメソッド。[2017-01-21])
コンパニオンオブジェクトの内容 説明
extends scala.runtime.AbstractFunctionN 「引数の個数に応じたAbstractFunctionクラス」(実質的にはFunctionトレイト)が親クラス。[2017-01-21]
つまりこのオブジェクトは「引数群から“ケースクラスのインスタンス”を生成する関数」として扱うことが出来る。
with scala.Serializable Serializableトレイトがミックスインされる。[2017-01-21]
apply() apply()メソッドが実装される。
つまりクラス名を用いたファクトリーパターンが使える。
(このメソッドは、AbstractFunctionクラスの実装。[2017-01-21])
unapply()
unapplySeq()
unapply()メソッドが実装される。
(基本コンストラクターの引数が可変長引数だった場合はunapplySeq())
つまりmatch式のcase(パターンマッチ)に指定できる。
toString() クラス名(のみ)を返す。[2017-01-21]
curried
tupled
これらのメソッドは生成されるのではなく、親のFunctionトレイトで定義されているもの。[2017-01-21]
curriedはカリー化された関数を返す。
tupledはタプルから“ケースクラスのインスタンス”を生成する関数を返す。

→ケースオブジェクト


ケースクラスの実体のイメージ。[2017-01-21]

case class Example(value1: String, value2: Int) {
  def hoge() = "zzz"
}

↓

/**
 * 生成されるクラスのイメージ
 */
class Example(val value1: String, val value2: Int) extends AnyRef with Product with Serializable {

  // 独自実装したフィールドやメソッド
  def hoge() = "zzz"

  // 値の一部を変更したインスタンスを返すメソッド(各引数の名前はフィールド名と同じで、デフォルト値に自インスタンスの値が指定されている)
  def copy(value1: String = value1, value2: Int = value2) = Example(value1, value2)

  // Productの各メソッド
  override def productPrefix: String = "Example"
  def productArity: Int = 2
  def productElement(n: Int): Any = n match {
    case 0 => value1
    case 1 => value2
  }
  override def productIterator: Iterator[Any] = ScalaRunTime.typedProductIterator(this)
  def canEqual(that: Any): Boolean = that.isInstanceOf[Example]

  // Objectの各メソッド
  override def toString(): String = ScalaRunTime._toString(this)
  override def hashCode(): Int = { scala.runtime.Staticsを使って算出 }
  override def equals(x: Any): Boolean = { 各フィールドを比較 }
}
/**
 * 生成されるコンパニオンオブジェクトのイメージ
 */
object Example extends ((String, Int) => Example) with Serializable {

  override def toString(): String = "Example"

  // AbstractFuntion2[String, Int, Example]((String, Int) => Example)のメソッド
  def apply(value1: String, value2: Int): Example = new Example(value1, value2)

  def unapply(x: Example): Option[(String, Int)] = Some((x.value1, x.value2))
}

ケースクラスの使用例。[2012-12-15]

scala> case class A(n: Int)
defined class A

scala> val a1 = A(1)	//コンパニオンオブジェクトのapplyメソッドが実装されるので、それを使ってインスタンスを生成できる
a1: A = A(1)
scala> val a2 = new A(2)	//newを使ったインスタンス生成も出来る
a2: A = A(2)

scala> println(a1.n)	//フィールドの取得は普通に出来る
1

//unapplyメソッドが実装されるので、パターンマッチを行うことが出来る
scala> a1 match {
         case A(n) => println(n)
       }
1

//equalsメソッドが実装されるので、普通に比較できる
scala> println(a1 == a2)
false
//copyメソッドが実装されるので、複製が簡単
scala> case class B(i: Int, j: Int, k: Int)
defined class B

scala> val b1 = B(123, 456, 789)
b1: B = B(123,456,789)

scala> val b2 = b1.copy()
b2: B = B(123,456,789)

scala> val b3 = b1.copy(j = 9999)	//名前付き引数を使って、フィールドの一部だけを変えたインスタンスを作れる
b3: B = B(123,9999,789)

ケースクラスのコンストラクターの可視性をprivateにすると、インスタンスを生成する手段が無くなる^^; [2012-12-15]

package example

object PrivateCaseClass {

  case class A private (n: Int)
  case class B private[example] (n: Int)
  case class C private[PrivateCaseClass] (n: Int)

  def main(args: Array[String]) {
×  println(A(1))
    println(B(1))
    println(C(1))

×  println(new A(1))
    println(new B(1))
    println(new C(1))

×  println(A.apply(1))
    println(B.apply(1))
    println(C.apply(1))
  }
}

apply()メソッドの呼び出しでも、「constructor A in class A cannot be accessed in object PrivateCaseClass」(コンストラクターにアクセスできない)というコンパイルエラーになる。

参考: nisshieeorgさんのツイート


Scala目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま