LL脳がscalaの勉強を始めたよ その30


Scalaコップ本の10章を進めていきますよー、10章では2Dレイアウトライブラリーの実装を進めつつScalaオブジェクト指向を学んでいっておりますよー

前回までに作成したのはライブラリの基底となる抽象クラスElementですなー

abstract class Element {
  // レイアウト要素となる抽象メソッド
  def contents:Array[String]
  // 要素の高さと幅を取得するメソッドもあり
  def height:Int = contents.length
  def width:Int = if(height == 0) 0 else contents(0).length
}

今回は、こいつをベースに進めていきますよー

クラスの拡張

前回作成したのはElementクラスは抽象クラスだったので、新しい要素オブジェクトが使えません(´・ω・`)。なので、Elementクラスを拡張して抽象メソッドcontentsを実装したサブクラスを作って利用しやすいようにしますよー

// extendsで拡張しますよー
class ArrayElement(conts:Array[String]) extends Element {
  // 抽象メソッドcontentsに結果contsを実装します
  def contents:Array[String] = conts
}

拡張(extends)は実際には次の2つの効果があるみたいデス

  • ArrayElementクラスはElementクラスの継承
  • ArrayElementクラスはElement型のサブ型になる

それぞれに付いてちょっとまとめてみますね

継承についてー

…継承だけじゃなくて型の関連になるわけですねー、あとは基本語句の復習としてArrayElementがサブクラスでElementクラスがスーパークラスになるっとc⌒っ゚д゚)っφ メモメモ...ここらへんは、まあ一般的なアレですね

一般的なアレ ⇒ サブクラスはスーパークラスの非公開メンバーでないものを継承して、ついでにオーバーライドもできてしまうよ!てきなアレ

とりあえずElement型を継承したのでArrayElement型はheightとかwidthとかのメソッドを使えるわけですねー。ちなみにElementクラスのように何も拡張していないクラスはコンパイラ的にはscala.AnyRefを拡張しているらしいデス

サブ型についてー

サブ型は「スーパークラスの値が必要な場所ではサブクラスの値を使える」という意味らしいんですが、さっぱりイメージが掴めないのでサンプルやってみますよー

// eはElement型なのに実際はArrayElement型になっている
scala> val e:Element = new ArrayElement(Array("hello"))
e: Element = ArrayElement@1474fc

…ようするにサンプルではElement型を拡張したサブ型ArrayElementは互換性があるよ!ということを言いたいみたいデス、ですよね?


ちなみにArrayElement型に対するElement型をスーパー型というらしいです。

合成ですよ

ここでArrayElementはArray[String]クラスを引数として受け取って内部のconsts配列の参照保持フィールドに埋めこむので合成関係にあるというらしいデス(´・ω:;.:...

…えーと、ArrayElementというクラス(型)の中にArray[String]というクラス(型)を取り込むことを合成と呼ぶって解釈でいいのかしら?それだとコンストラクタにパラメーター取るようなものは全部合成になるような…その場合は同じ型だったら合成にならないとか…い、いまいちわからないです(´・ω・`)

とりあえず異なる型を引数で取り込むことで内部的に組み合わせる、ぐらいで考えておこうかしら…あとはやっていくうちにわかれば…いいな(´・ω・`)…もしくは教えてエライ人、いやまじで

メソッドとフィールドのオーバーライド

ScalaJavaと違ってメソッドとフィールドが区別されない(同じ名前空間に属する)のでフィールドをメソッドでオーバーライドしたりが出来るみたいです(`・ω・´)

なので、単純に値を返す実装しかしていなかったElementクラスの抽象メソッドcontentsをフィールドでオーバーライドしてしまいますね

class ArrayElement(conts:Array[String]) extends Element {
  // 抽象メソッドcontentsをフィールドでオーバーライド
  val contents:Array[String] = conts
}

うん、なんかすっきりしましたなー

ちょっと蛇足ー

Javaだとフィールドとメソッドに同じ名前を使っても区別されるけどScalaでそれやると上書きされちゃうからダメね要注意ね(゚∀゚)って書かれていてJavaはそんななのか…と思ってたらPythonだとあっさり上書きされちゃうでゴザル(´・ω・`)

# Pythonのコードデス
class hoge():
  # メンバ変数を定義
  f = "1"
  # 同名のメソッドを定義
  def f(self):
    return 1

# インスタンスを生成
>>> h = hoge()
# メンバ変数にアクセスしたらメソッドを掴んでました
>>> h.f
<bound method hoge.f of <__main__.hoge instance at 0x83a090c>>
# メソッド実行すれば値が取り出せますな(´・ω・`)
>>> h.f()
1

ちなみにScalaだときっちりコンパイラが怒ってくれますよ

class Hoge {
  var f = "1"
  def f = 1
}
// きっちりエラーですなー
<console>:6: error: method f is defined twice
         def f = 1
             ^

// ちなみにvar をvalに変えると次のようなエラーになりましたよ
<console>:6: error: value f is defined twice
         def f = 1
             ^

まあ、フィールドとメソッドに同じ名前使うなわ、ってことですな

Scala名前空間

Javaの4個に比べてScala名前空間は次の2つだけみたいです

  • 値:フィールド、メソッド、パッケージ、シングルトンオブジェクト
  • 型:クラス、トレイト

Scalaがフィールドとメソッドを同じ名前空間で管理しているのは、パラメーターなしメソッドをフィールドでオーバーライドできるようにだそうです。統一形式アクセスの原則ですね(゚∀゚)

パラメーターフィールドの定義

前の方で定義したArrayElementクラスの定義は若干冗長なので修正してみますよ

// 内部でフィールドに定義(フィールドを初期化)していたパラメーターを
// パラメーター部でまとめてしまいますよ
class ArrayElement(val contents:Array[String]) extends Element

パラメーターにvalプレフィックスをつけることでパラメーターとフィールドに同じ名前を津定義することができるっぽいです(・∀・)フィールド初期化のためのパラメーターならこうやって省略したほうがすっきりしそうですなー

プレフィックス(接頭辞)もいろいろ

上のサンプルではプレフィックスとしてvalを使ったのですが、varを使って再代入可なフィールドを作ることもOKデス

またプレフィックスとして privateやprotectedやoverride修飾子を追加することもOKラシイデスネ

ちょっとサンプル書いてみますよ

// スーパークラスの定義
class Cat {
 val dangerous = false
}

// サブクラスを定義
class Yashichi (
  // 非公開で再代入可のフィールドを定義
  private var makihige:Integer,
  // サブクラスのみアクセス可 の再代入不可のフィールドを定義
  protected val husahusa:Boolean,
  // 親クラスをオーバーライドする再代入不可のフィールドを定義
  override val dangerous: Boolean
) extends Cat

うん、省略できるところはすっきりさせるのがよさそうですねー

ちなみにmakihigeがvarでprivateなのは天気によって巻き方度合いが変更される個体限定の特性だからです。でもhusahusaは継承できますよ(´・ω・`)

スーパーコンストラクターの呼び出し

さて定義したArrayElementをさらに拡張しますよー

ArrayElementはパラメーターとしてArray[String]をとってたけども、String単品を取るのもあったら便利ね(・∀・)ってことでサブクラスLineElementを作りますよ

とりあえずサンプルですな、新しい内容が盛り沢山デス

// LineElementのパラメーターをArrayElementのパラメータとするように
// スーパーコンストラクターを呼び出して拡張しますよ
class LineElement(s:String) extends ArrayElement(Array(s)){
  // 実装済みのスーパークラスのメソッドをオーバーライドするので
  // override修飾子をつけますよー
  override def width = s.length
  override def height = 1
}

さて、細かい内容をまとめていきますよー

スーパーコンストラクター

LineElementではパラメーターとしてStringをとるのですが、スーパークラスArrayElementではArray[String]をパラメーターとして渡す必要がありますな

なのでextends文法の中でs:StringをArray(s)に変換してスーパークラスに渡してやりますデス

こうすることで、パラメーターの形式が異なるスーパークラスコンストラクターに適切なパラメーター(上記例ではArray[String])を渡すことが出来るのですな

override修飾子

スーパークラス実装済み(具象)メンバーをオーバーライドする場合は必ずoverride修飾子が必要らしいです

// ArrayElementを拡張したクラスを作成
class LineElement2(s:String) extends ArrayElement(Array(s)){
  // override修飾子をはぶきますよ
  def width = s.length
}

// override宣言しろって怒られました
<console>:8: error: error overriding method width in class Element of type => Int;
 method width needs `override' modifier
         def width = s.length

ちなみにスーパークラスにないものをオーバーライドしても怒られますねー

// ArrayElementを拡張したクラスを作成
class LineElement3(s:String) extends ArrayElement(Array(s)){
  // スーパークラスにないものをoverride
  override def depth = s.length
}
// オーバーライドするものがねーよ!と怒られました
<console>:8: error: method depth overrides nothing
         override def depth = s.length
                      ^

これは、その気がないのにオーバーライド(´・ω・`)みたいな単純ミスをコンパイラレベルで判定するための仕様だそうです。ちなみに凡ミスのオーバーライドを「不測のオーバーライド」と呼ぶとこことですね…

まあ、動作を起こす前にエラーが検出できるはいい事だ_|\○_ヒャッ ε=\_○ノ ホーウ!!

でも抽象メンバーは省略してOKよ

ただし、Elementに対するArrayElementのように抽象メンバーを実装(形式的にはオーバーライド?)の場合はつけてもつけなくても良いそうです。

// 一番最初の例
class ArrayElement(conts:Array[String]) extends Element {
  // 抽象メソッドのオーバーライドなのでoverrideがいりませぬ
  def contents:Array[String] = conts
}

まあ、実装なのかオーバーライドなのかを区別する意味で付けない方が一般的なんですかね?どうですかね?いや、なんとなくそう思っただけです。

いじょー

とりあえず今回までの成果はこんな感じですかねー

// 抽象クラスElementを定義
abstract class Element {
  def contents:Array[String]
  def height:Int = contents.length
  def width:Int = if(height == 0) 0 else contents(0).length
}

// Elementのサブクラスを拡張して実装
class ArrayElement(val contents:Array[String]) extends Element

// ArrayElementをさらに拡張 
class LineElement(s:String) extends ArrayElement(Array(s)){
  override def width = s.length
  override def height = 1
}

まだまだ続くよ10章は(´・ω・`)、次はポリモーフィズムとかそんな感じですかねー