クラス・継承・トレイト

1. クラス

class で始まる単語で、”クラス”が定義可能です。”変数”と”振る舞い”の塊を定義可能です。
ここでは、『人間(Human) として、名前(name)が指定出来て、速度(speed)が時速5Kmで、歩く(walk)事が出来る 変数を宣言できる』型を定義しています。

class Human(val name: String, nickname:String) {
   val speed = 5
   def walk = {
     println (name + "は時速" + speed + "Km で歩く")
   }
}

こんな感じで利用できます。

val m = new Human("Jonathan","Jon")
m.walk
// Jonathanは時速5Km で歩く
println(m.name)
// Jonathan
// NG ・・・ println(m.nickname) 
// val が宣言されていないため、ニックネームは外から参照できない

2. 継承

人間だけでなく、動物全般のクラスを作る事になった場合に、共通の振る舞いをする部分を切り出す事を考えます。

class Animal(val name: String, nickname:String) {
  def walk(speed: Int) = {
    println (name + "は時速" + speed + "Km で歩く")
  }
}

walk が定義された 動物(Animal)のクラスを定義して、それを継承したHuman を定義します。

class Human(override val name: String, nickname:String) extends Animal(name, nickname) {
  val speed = 5
}

同様に犬(dog)が定義できます。

class Dog(override val name: String, nickname:String) extends Animal(name, nickname) {
  val speed = 3
}

こんな感じで呼出側は利用できます。

val d = new Dog("Winston","Wins")
d.walk(d.speed)
// Winstonは時速3Km で歩く
trait Animal {
  val name: String
  val speed : Int
  def walk = {
    println (name + "は時速" + speed + "Km で歩く")
  }
}
// 牛
class Cow {
}
object Cow{
  def desc = println("牛です")
}

3.トレイト

これまでの例のwalkで speedを毎回指定するのは面倒です。 必ず、子クラスで定義される事が約束されていて、親クラスで利用できる変数が定義できます。 このように”約束事”が定義可能なクラスの事をトレイト(trait)と scala では呼んでいます。 Javaで言うインターフェース(interface)の事ですが、scalaでは実際の処理も定義可能です。 Animalを変更します。

class Cat(name: String) {
  def apply(age:Int) = println("これはapplyから呼ばれています(" + name + ":" + age + "歳)")
}

val myCat = new Cat("たま")
myCat(5)
// これはapplyから呼ばれています(たま:5歳)

Human と Dog も変更します。 子クラスでspeedが宣言されていないとコンパイルエラーになります。

class Human(val name: String, nickname:String) extends Animal {
  val speed = 5
}
class Dog(val name: String, nickname:String) extends Animal {
  val speed = 3
}

呼び出し側では、walkの引数にspeed を指定する必要がまったく無くなります。

val m = new Human("Jonathan","Jon")
m.walk
// Jonathanは時速5Km で歩く
val d = new Dog("Winston","Wins")
d.walk
// Winstonは時速3Km で歩く

トレイトは複数指定可能です。例えば、『吠える(bark)』を犬(Dog)や馬(Horse)にだけ、振る舞わせたい場合には、以下のように with で指定可能です。

// 威嚇
trait Threatable {
  def bark:Unit
}

class Dog(val name: String, nickname:String) extends Animal with Threatable {
  val speed = 3
  def bark = {
   println("バウバウ!")
  }
}

class Horse(val name: String, nickname:String) extends Animal with Threatable {
  val speed = 7
  def bark = {
   println("ヒヒーン!")
  }
}

with は複数指定可能です。 共通化したい振る舞いを細かく宣言して、組み合わせる事が推奨されているようです(影響が局所化)。

4.抽象クラス

トレイトとほぼ同じですが、コンストラクタ引数を指定可能な抽象クラスが”abstract”です。

// 武器(攻撃力)
// コンストラクタ引数で攻撃力を指定している
abstract class Weapon(val offensivePoint:Long ) {
  def descOffensive = println("この武器の攻撃力は" + offensivePoint + "です。")
}

// 剣
class Sword(override val offensivePoint:Long) extends Weapon(offensivePoint)

以下のように利用出来ます。

val myWeapon = new Sword(1000)
myWeapon.descOffensive
// この武器の攻撃力は1000です。

5.コンパニオンオブジェクト

英語ではCompanionは仲間と言った意味があるようです。 あるクラス名と同名の『object』宣言した定義の事をコンパニオンオブジェクトと呼びます。 そのオブジェクトのメソッドを、インスタンス化せずに呼ぶことができます。 以下のようにdescをいきなり呼ぶことができます(Javaで言うところのstaticメソッド)。

Cow.desc
// 牛です

また、apply と言う特殊なメソッドを使ってインスタンス化出来ます。 object 側のprivate な関数もclass 側から参照が可能になっています。

// 牛
class Cow(name: String){
  def descCow() = Cow.desc(name)
}

object Cow{
  private val cowType: String = "乳牛"
  def apply(name: String) = new Cow(name)
  private def desc(name:String ) = println(cowType + "です。名前は" + name + "です")
}

// インスタンス作成
val myCow = Cow("モー太郎")
myCow.descCow
// 乳牛です。名前はモー太郎です

6.applyについて

apply は特殊なメソッドです。その奇術を省略する事ができます。そのため、上の例では Cow(“モー太郎”) は、Cow.apply(“モー太郎”)の省略した形です。 以下の例ではコンパニオンオブジェクトのインスタンス化のみではなく、一般的なメソッドでも利用できる例を示しています。 このapply メソッドは別途紹介する”unapply”と対になるメソッドです。