それが僕には楽しかったんです。

いろんなレイヤーに居ます

全てのプログラマに捧げるScala入門 クラスとオブジェクト、ケースクラス

はじめに

前回の続きで、今回はクラスとオブジェクトについてまとめていく。
その前に、メソッドについて触れていなかったのでそこもまとめる。

メソッド

Scalaにおけるメソッドとは次の形をとる。

(private or protected [this or package_name]) def methodName(arg0: DataType, arg1: DataType, ... argN: DataType): ResultDataType = ???

これだけではわかりにくいのでざっくりと解説すると
アクセス修飾子やthis, package_name等は必要に応じて任意でつける。
defがメソッドを宣言するキーワードとなり、0個以上の引数を持つ。
最後に戻り値のデータ型、つまり結果型を記述し=の後に式を書いていく。
式ということなので{}式を用いて複数の式を入れることも可能。

privateやprotectedをつけない場合はpublicとみなされる点はJavaと異なるので注意が必要である。
[this]とすればそのクラス内だけから、[package_name]とすると同一パッケージ内のみからのアクセスが可能となる。
よく使うのは次のような形となる。

private [this] def getXXX(): Int = 0

クラス

Scalaのもつクラスはコンストラクタの記法を除いてほとんどJavaと同じである。
というのもコンストラクタを特に使用しない場合は次の様に記述するからです。

//これはScalaのクラス
class ClassName{
}

これでコンストラクタを使う場合は次のようになる。

class ClassName(param1: DataType, ... ,paramN: DataType){
}

()で囲んだパラメータリストをクラス名の横に付与するとそれがコンストラクタになる。
例として、コンストラクタの値をフィールドに代入するコードを上げると次のようになる。

class SampleClass(num1: Int, num2: Int){

    val number1: Int = num1
    val number2: Int = num2
}

このようなクラスはJavaインスタンスを生成するようにnewでインスタンスを生成することができる。
コンストラクタに対しての引数の渡し方も同様に次のようにできる。

val inst: SampleClass = new SampleClass(1, 2)

継承

ScalaのクラスはほとんどJavaと同じわけなので継承に関しても次のようになる。

class SubClass extends SuperClass{
}

見かけは同じなのだが少しだけ違いがある。
クラスにおいて継承した既存のメソッドをオーバーライドする場合、Javaでは@Overrideというアノテーションを付与したが
Scalaにおいてはoverrideキーワードになる。

なので、仮にtoStringメソッドをオーバーライドするときは次のようになる。
overrideキーワードをつけるのは継承したメソッドだけでなくオーバーライドする全ての場合に適用される。

override def toString(): String = ???

補助コンストラクタ

ここまでの内容を把握してみて、ひとつ疑問に思うことはないだろうか。
それはコンストラクタのオーバロードをどうするかという問題である。

それに関しては解決策があり、補助コンストラクタを用いるものである
これはJavaでいうコンストラクタのオーバロードと似ているが決定的にことなる部分がある。

まずは補助コンストラクタの使い方から見てみよう。
例はコップ本にある分数を扱うクラスを参考に一部改変し、簡略化したものを使う。

object Sample{
  def main(args: Array[String]):Unit = {

    val sample: Rational = new Rational(1,2)// 1/2

    val ratinal: Rational = new Rational(2)

  }
}

class Rational(val n:Int, val d:Int){

  //コンストラクタから値を受け取り初期化
  val numer: Int = n
  val denom: Int = d

  def this(n:Int) = {
    this(n,1)
    //ここに処理があってもいい
  }

  //toStringメソッドをオーバーライド
  override def toString(): String = numer + "/" + denom

}

このコードにおいて、補助コンストラクタは次の部分である

def this(n:Int) = {
    this(n,1)
    //ここに処理があってもいい
  }

このようにdef thisというメソッドが補助コンストラクタとなる。
しかしこの補助コンストラクタというものを使うときには
thisを使い必ず先頭でそれより前に定義されたコンストラクタを呼び出す必要がある。

オブジェクト

Scalaにおいて全てはクラスであり、値はオブジェクトとして存在しメソッドは何らかのオブジェクトとなっている。
その状態を考えるとクラスに属するstaticなものを生成することができない。

しかし、その状態は不便なのでobjectキーワードがそれを解決する。
objectがあると何が嬉しいのかというと、objectで指定したシングルトンオブジェクトを生成することが出来る点である。
シングルトンオブジェクトであるが故に、そのオブジェクト固有のメソッドやフィールドを扱うことができる。

シングルトンオブジェクトを作ることができるなら、それによる恩恵を受けることができる。
そうなると、ユーティリティメソッドやオブジェクトのファクトリメソッドなども扱うことができる様になる。

それでは早速使ってみる。
といっても宣言から継承などほとんどクラスと変わらない上に、実はもう一度登場している。
それはHelloWorldを出力した時である。

object Sample{
    def main(args: Array[String]): Unit = {
        println("hello,world")
    }
}


この時にすでに使っている。
これによりmainメソッドがJavaでいうところのstaticメソッドになっている。
唯一といってもいい大きな違いはコンストラクタとしてパラメータを受け取れない点にある。

ケースクラス

最後に、クラスでもオブジェクトでもない特別なクラスを紹介する。
それはケースクラスというものでこの後に登場するパターンマッチという機能にも関連するものである。


ケースクラスとは次の様に宣言する。

case class User(name: String, age: Int)

これを見ると早いのだが、classの宣言の前にcaseキーワードが付いただけである。

一見なんら変化が無いように思えるがケースクラスには次のような利点がある

  • コンストラクタ引数にvalが付いた状態で自動的にそのクラスのメンバとして宣言される
  • パターンマッチに使える
  • 便利なメソッドが自動生成される

今回はこれの特に自動生成されるメソッドについて、とくにapplyについて触れる。

applyメソッド

実際、自動生成されるメソッド群のなかでこれが一番使われていると思う。
applyメソッドはそのケースクラスのインスタンスを生成する。
ということはこれを使うことでnewをする必要がなくなる。

上記のUserクラスをそのまま例で使う。
caseクラスのインスタンス生成の仕方は以下の通りである

val user :User = new User("scala", 20) //これでも生成できる
val user :User = User.apply("scala", 20) //こっちでもok 

おわりに

ここまでで、一通りクラス、オブジェクト、ケースクラスについては解説した。
次はJavaでいうinterfaceに類似した機能をもつtrait(トレイト)について解説する。