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]というクラス(型)を取り込むことを合成と呼ぶって解釈でいいのかしら?それだとコンストラクタにパラメーター取るようなものは全部合成になるような…その場合は同じ型だったら合成にならないとか…い、いまいちわからないです(´・ω・`)
とりあえず異なる型を引数で取り込むことで内部的に組み合わせる、ぐらいで考えておこうかしら…あとはやっていくうちにわかれば…いいな(´・ω・`)…もしくは教えてエライ人、いやまじで
メソッドとフィールドのオーバーライド
ScalaはJavaと違ってメソッドとフィールドが区別されない(同じ名前空間に属する)のでフィールドをメソッドでオーバーライドしたりが出来るみたいです(`・ω・´)
なので、単純に値を返す実装しかしていなかった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
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 ^
まあ、フィールドとメソッドに同じ名前使うなわ、ってことですな
パラメーターフィールドの定義
前の方で定義した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 }
さて、細かい内容をまとめていきますよー
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章は(´・ω・`)、次はポリモーフィズムとかそんな感じですかねー