面白くなってきた。第3章。
継承には2種類がある。
- 契約あるいは型の継承
- 実装の継承
2. だけが目的なら、継承ではなくコンポジションや転送を使用すべき。だと思う。
3.1 拡張したクラス
Object←Attr←ColorAttrという例。
3.2 拡張したクラスのコンストラクタ
コンストラクタは、メソッドではなく、継承されません。
これか!! (「2.5.1 コンストラクタ」の謎が解けました)
言われてみれば、 C++でもコンストラクタは継承されない。ただ、C++のコンストラクタはメンバ関数のはずなので、「メソッドではない」という言い回しが気になるけど……。単なる定義の問題かしら。
3.2.1 コンストラクタの順序の依存性
ウオーめんどくさい!
めんどくさいけど、難しくはない。下手な設計さえしなければ、問題になることは少ないと思われる。
3.3 メンバーの継承と再定義
3.3.1 オーバーライド
メソッドをオーバーライドするとき、アクセス制限を緩和することはできても、制限することはできない。びっくり!と思ったけど、
スーパクラスよりもメソッドへのアクセス制限を厳しくすると、スーパークラスのインスタンスが使用できたところでサブクラスのインスタンスが使用できなくなりますので、スーパークラスの契約を破ることになります。
いやまったくその通りです。C++で厳しくしたことがあって、何故だったか思い出してみると、親クラスの設計ミスってて明らかにprivateにすべき関数がpublicになってて、おまけに諸事情で親クラスを変更できなかったからだった……。
3.3.2 フィールドの隠蔽
フィールドは、オーバーライドできません。フィールドは隠蔽できるだけです。
これが次の項で結構重要になる。
3.3.3 継承されたメンバーへのアクセス
メソッドへのアクセスは「実際のクラス」に基づいて使用される実装が決まる(C++と同じ)けど、フィールドへのアクセスは「宣言されている参照型」によって決まる。これが、フィールドは隠蔽できるだけということの意味。
純粋論者は、クラスはprivateなデータだけを持つべきだと主張するでしょうが、自分のスタイルは自分で決めてください。
投げた!
僕は今のとこかなり純粋論者寄りで、「定数を提供する」以外に、フィールドをprivate以外にする妥当な理由が思いつかない。「書く / 読むコードが少なくて済むからちょっと楽」というのは、かなり危ういと思う。
3.3.4 アクセス制限とオーバーライド
メソッドは、それにアクセスできる場合だけオーバーライドできます。
なんと!
これは、privateメソッドをオーバーライドできないことを意味している。たとえばTemplate Methodパターンでは、privateではなくprotectedなメンバ関数を呼び出すようにしないといけないということだ。でも言われてみると、至極まっとうな理屈な気がする。Javaに染められていく……。
3.3.5 staticメンバーの隠蔽
staticメンバーはオーバーライドできないが、そもそもstaticメンバーはクラス名を通してアクセスすべきなので、別に問題ではない。
3.3.6 super予約語
superを通して親クラスにアクセスできる。
C++ではどうしてたっけ?て、一瞬思い出せなかったけど、スコープ解決演算子を使うのであったな。Javaに染められていく……。(本日2回目)
3.4 型の互換性と変換
3.4.1 互換性
やはり、Javaのキャストはdynamic_cast的なものらしい。ダウンキャスト(狭くする変換)には必ずキャストが必要な由。
3.4.2 明示的な型キャスト
3.4.3 型の検査
instanceof演算子について。インスタンスの実際の型を調べてカスタマイズした実装を提供できるのは、素敵だ。が、if文が出てくるのがどうもモサい感じがする。
3.5 protectedが本当に意味すること
節のタイトルからしてまずカッコいい。思わせぶりだ。
そして衝撃の事実が明らかになる。
protectedと宣言されたメンバーは、そのクラスのパッケージ内のどのコードからも、アクセスできます。
!?!?
同じパッケージ内のクラス同士は、完全に信頼でき、お互いの契約を破ったりしないと考えられているからです。
僕には自分がnaiveな方だという自覚があるが、そんな僕でもこれにはnaiveすぎて泣けてきた。ハッピーワールド!
ていうか、真面目な話、パッケージをそういう風に設計せにゃならんということですね。本当に密接な関連のあるものだけを一つのパッケージに入れないといけないということになるはずだ。C++の名前空間どころの騒ぎじゃまったくない。
3.6 メソッドとクラスをfinalにする
Javaと付き合っていく間ずっと、finalには複雑な想いを抱き続けることになる気がしてならない。
finalは、constではない!
finalにconstの影を見てはfinalにも失礼だし、finalと正面から向き合って付き合っていきたいけど、今のところあんまり自信がない……。
finalがいかにconstでないか、差し当たり今わかっている2点を述べる。
- メソッドにつけるfinalは、メソッドがオーバーライドできないことを保証するものであり、finalなフィールド以外にアクセスできなくなるわけではない。(constメンバ関数を作れない)
- 変数は参照型なので、finalをつけても参照がfinalになるだけである。オブジェクトへの変更を防ぐことはできない。
1. への補足。
もちろん、finalが依存しているフィールドはfinalかprivateであるべきであり、そうでなければ、……
それを保証してはくれないのだ、Javaの人は……。
そしてfinalがconstではなく何であるかについては、あえてここでは語らない。
3.7 抽象クラスと抽象メソッド
抽象クラスと抽象メソッドについては、たぶんC++と同じ。
abstractメソッドを持つクラスは、abstractと宣言しなければなりません。
ここだけが違いかな?
3.8 Objectクラス
Objectクラスは以下の6つのユーティリティメソッドを持っている。(つまり、Javaのすべてのクラスはこれらのメソッドを持っている)
public boolean equals(Object obj)
public int hashCode()
protected Object clone() throws CloneNotSupportedExeption
public final class<?> getClass()
protected void finalize() throws Throwable
public String toString()
名前的にデストラクタっぽいのきた!!
あとは、2つのオブジェクト間でequals()がtrueを返すときは、2つのオブジェクトのhashCode()がtrueを返すべき。という重要なことが書いてあった。何となくhashCode()は同一性(==によってテストされる性質)と紐付いているイメージ(根拠はない)があったけど、違ったということか。ううむ。ちなみに、Objectクラスの実装では、同値性と同一性は一致している。(2つのオブジェクトについて、equals()と==は同じ結果を返す)
3.9 オブジェクトの複製
3.9.1 複製に対する戦略
クロネアブルインタフェース、悲しい感じがする。(脚注が悲しみを助長している)
それはともかく、こうやって名前がつけられる(間違ってるけど)のがインタフェースの強みだなと感じる。C++では一つ一つがグッドプラクティスとして(Scott Meyersとかによって)語られていた事柄が「Cloneableインタフェース」と「clone()メソッド」に集約される。悪くない。
3.9.2 正しい複製
clone()関数でfinalフィールドの値を明示的に設定することはできない。うーん、これはちょっとモサいぞ……?かわりにコピーコンストラクタを使えばうまくいくよ!って書いてあるけど、その使い分け、めんどくない……?
3.9.3 浅い複製と深い複製
一項前で既に議論されている、シャローコピーとディープコピーの話題。
3.10 クラスの拡張:どのように、そしていつ
IsA関係とHasA関係の適切な使い分けの話。この辺、昔Javaの勉強をちょっとだけし(ようとし)たとき、「乗り物」と「自動車」の関係で喩えられたりしてて正直意味不明だった記憶があるけど、あの本なんだったか思い出せなくて、気になる。今なら理解できるのだろうか。
3.11.1 拡張可能なフレームワークの設計
なんか急にサンプルコードの設計が丁寧になっててウケる。このコードは、Javaに慣れないうち、クラス設計・実装のリファレンスにできそうな気がする。
そして、セキュリティホールを探す練習問題くるかなと思ったら、やっぱりきた!
とりあえずprobe()を一しきり呼んでブツをコピーしたらあとはやりたい放題というのは思いついた。何にせよいっぺん実装してみないとな。
そういやJDKまだ入れてないことに気づいてまじめに愕然としている。いい加減コード書かないと知識が飽和してしまう。
3.12 単一継承と多重継承
多重継承の問題をあげつらってから第4章のインタフェースに流すの、にくい。
第3章おわり。
Javaのいいとこもよくないとこもちょいちょいわかってきた感じがする。そしていい加減コード書こうと思います。