Effective Java 一人読書会(3章:すべてのオブジェクトに共通のメソッド編)

| コメント(0) | トラックバック(0)

前回からの続き。

1日2項目ペースのつもりで...と考えていたのに早くも乱れ気味です。誕生日迎える前には読み終わりたいところだ。
項目6までは以前読んだことがあるのですんなりといけたが、こっから先が大変そう...。

3章はすべてのオブジェクトに共通のメソッド ってことですべてのクラスの親に当たるObjectにあるメソッドの話がメイン。

---
7.equals()オーバライド時の注意点

-どんな時にオーバライドするの?
 →参照が同じかどうかではなく、参照先の中身が同じかどうか(論理的等価性)を知る必要があり、親クラスで使えそうな実装がされていないとき。

-equals()は好き勝手に書いちゃダメ。

Objectのリファレンスから引用

equals メソッドは、null 以外のオブジェクト参照での同値関係を実装します。
* 反射性 (reflexive):null 以外の参照値 x について、x.equals(x) は true を返す
* 対称性 (symmetric):null 以外の参照値 x と y について、x.equals(y) は、y.equals(x) が true を返す場合だけ true を返す
* 推移性 (transitive):null 以外の参照値 x、y、z について、x.equals(y) が true を返し、かつ y.equals(z) が true を返す場合に、x.equals(z) は true を返す
* 整合性 (consistent):null 以外の参照値 x および y について、x.equals(y) を複数呼び出すと常に true を返すか、常に false を返す。 これは、オブジェクトに対する equals による比較で使われた情報が変更されていないことが条件である
* null でない任意の参照値 x について、x.equals(null) は false を返す(*)
(*)...本ではこの項目を非null性と名付けている。

上の3つは大学の数学でもでてくる反射律、対称律、推移律だね。なつかすぃ。
本ではこの中でも特に対称性と推移性が守られていない例を紹介している。
結論としてはインスタンス化可能なクラスを継承してフィールドを追加した子クラスを作り、拡張した場合、厳密なequals()は実装できない。(親と子を比較しようとするとおかしなことになる。)これは本のコード例を見た方が早い(p28~30)。
解決方法としてコンポジットを使う例が紹介されている(p31)。

-非null性のチェックにnullチェックはしない。型チェックでnullも一緒にはじく。if(!(obj instanceof Hoge)) return false;
ここまで読んで昔GAのプログラムを作っていたときに遺伝子クラスにequals(Gene gene)って書いてたなぁと思い出す。正しくはequals(Object obj)だよなぁ。(この項の最後の方でも同様のことについて触れている。これはオーバライドじゃなく、オーバロードだ!)どんな型でもequalsに突っ込めるようにするのが正しい。

(p32) -くおりてぃーの高いequals()の作り方のまとめ
 1.自分自身かどうかを==で判定(反射性) →親クラスにequals実装がなければObjectのequalsがそのまま使える。
 2.型検査(instanceof使って)そのままキャスト
 3.2つのフィールドの一致検査(比較する順番にも注意する。なるべく評価コストが小さく、2つのオブジェクト間で異なりそうなものから比較すればパフォーマンスが良くなる。)

doubleとfloatの比較の注意点が書かれている。DoubleやFloatクラスのリファレンスにも同様のことが載っている。

System.out.println(0.0 == -0.0);//true

System.out.println(Double.doubleToLongBits(0.0) == Double.doubleToLongBits(-0.0));//false

これしらんかった(;´Д`)
でもここまで気にする必要あるんかいな?(フィールドにdoubleを滅多に使わない人の一意見)

 4.対象性、推移性、整合性を最後にテスト
(アレ?残りは?→㌧でもない実装をしない限り残りの2つは満たされているはず)

-項目の最後に捕捉説明
 -equals上書きしたらhashCodeも上書き(次項目)
 -賢すぎる実装はしない
 -信頼できない外部リソースに依存するequalsは作らない 悪例)java.net.URL#equals() 整合性に欠けるって話だね。


--------------
8.equals上書きしたらhashCodeも上書き
全然しらんでした....( ;´Д`)

再びObjectクラスのリファレンスより引用

hashCode メソッドの一般的な規則を次に示します。
* Java アプリケーションの実行中に同じオブジェクト上で複数回呼び出される場合は必ず、このオブジェクトに対する equals による比較で使われた情報が変更されていなければ、hashCode メソッドは同じ整数を一貫して返さなければならない。ただし、この整数は同じアプリケーションの実行ごとに同じである必要はない
* equals(Object) メソッドで 2 つのオブジェクトが等価とされた場合、どちらのオブジェクトで hashCode メソッドを呼び出しても結果は同じ整数値にならなければならない
* equals(java.lang.Object) メソッドで 2 つのオブジェクトが等価でないとされた場合は、これらのオブジェクトに対して hashCode メソッドを呼び出したときに、結果が異なる整数値にならなくてもかまわない。しかし、等しくないオブジェクトについては異なる整数値が生成されるようにすれば、ハッシュテーブルのパフォーマンスを上げることができる
equalsを上書きして参照が違くても中身同じならtrueを返すような実装をした場合、hashCodeも同じ値を返すようにしないと2番目の規則に反することになってしまう。

-hashCode()に求められるモノ
 -意味的に同じオブジェクトは同じハッシュ値を返す
 -違うオブジェクトは異なるハッシュ値を均等に分散させて返す

これらが守られないとハッシュ系コレクションクラス利用時におかしな振る舞いをしてしまう。

p37で理想的なhashCode()を実装するためのガイドラインがstep形式でまとめられている。
この部分を読み終わった後、試しにStringのhashCodeの実装を見てみたが、ハッシュ値の計算部分が正にその通りになっていた。
更にStringではハッシュ値をデフォルト0でインスタンスフィールドに保持していて、0ならハッシュ計算。0以外ならそれを返す...といったようにキャッシュ化されていてもう少し賢い実装になっている。

-パフォーマンスよりもハッシュ値が重複しないように心がける。
 →Hashが複数のLinkListに化けてしまう恐れがある。

-Java1.2より前のString#hashCode()の黒歴史


--------------
9.toString()は常に上書き
まぁ、これは知ってた。特にdebug時に大活躍だよなぁ。

-ObjectのtoStringはクラス名@符合無し16進ハッシュ値なので読みづらいし、意味がない。

-toString()を上書きするならそのクラスのオブジェクトが含む意味のある情報を全て返すべき。→あまりに長くなるなら要約で。

-toString()ではきだした情報へのアクセサを作る。→じゃないとtoStringに依存する実装をされて後からフォーマットが変更できなくなる。

-ドキュメントにどういう意図で作ったのかも書く(まぁ、これはtoStringに限ったことではないが)
 フォーマットが変更されるのか、されないのかも書く。変更しないつもりならフォーマットの詳細も。


--------------
10.clone()の注意点
cloneも結構いい加減に考えていたけど色々と制約があるようです。

それにしてもこの節、あんまりまとまってなくて分かりづらい。前橋さんの[Java謎+落とし穴]でも似たような話があったけどそっちの方が分かり易かったな。

前提として親クラスが正しくcloneを実装していなければならない。(コンストラクタが使えないのでsuper.cloneを頼るしかない)
cloneを実装するときはマーカーインターフェースCloneable(スペルミス?仕様です)をimplementsしなくてはいけない。

実装クラスが
 -プリミティブか不変クラスの参照しかもたない→super.clone()だけでok
 -可変クラスのインスタンスを持つ→clone元と同じ参照を持つので問題アリ(cloneは浅いコピー。)

p46-48 深いコピー(deep copy)の話
-クラスに可変オブジェクトをfinalで持つインスタンスフィールドがある→それclone()実装無理。
-インスタンスフィールドに配列を持つ→配列のcloneも浅いコピーなので各要素もコピーするように書き換える必要があり。

-super.clone()を呼び出してから全てのフィールドを真っ白にしてそこからclone元と同じになるように再現していくという方法もあり(ただし、パフォーマンス低)

-cloneで複製中のオブジェクトに対してfinalでないmethodは呼ばない。オブジェクトの状態が作られる前にmethodが呼ばれる可能性がある(項目15。これもしらんかった~。)

実装時のまとめ
-Cloneableをimplementsして、publicでcloneを上書き(Object#cloneはprotected。外からは呼び出せない)。clone内でsuper.cloneを呼んで可変クラスのobj参照をそれのコピーへの参照(deep copy)に修正していく。(多分その可変クラスのcloneを呼ぶことになる)
-不変クラスやプリミティブは手を加えなくてもok(だから不変クラスにcloneなんていらない)。ただし、オブジェクト生成日時といったデータなら当然のごとく作り直しだ。

-clone実装したくない
 コピーの代替手段(コピーコンストラクタ)か複製手段を最初から提供しない。

-コピーコンストラクタ
public Hoge(Hoge hoge)
(自分自身のクラスが型の)一個の引数を受け取るコンストラクタ。第1項目でも出てきたstatic factory method等でも代用可。メリットとしてはcloneの規約に従わなくても良いので自由に実装できる。可変オブジェクトをfinalで持つインスタンスフィールドがあってもコンストラクタならナントカできる。

--------------
11.Comparableのimplement
そのクラスのインスタンスがnatural Oderingを持つ場合に実装する。
比較なのでequalsと似たような数学的性質を持たなければならない。

compareToはequalsと違ってClassCast or NullPointer Exceptionを発生させてもok。だから型検査はせずにキャストとして代入するだけでok。

...といってもjdk1.5以降、Comparableはgenericsを使用するようになったのでClassCastExceptionが発生するのはあんまないかもなぁ。

return (a-b);の形に注意。
これ、楽だからついやっちゃうんだけど、aとbの差が2^31-1のときにしか使えないのね。
aが+、bが-で、aとbの絶対値が大きいとオーバーフローするので注意する。


4章に進みます。

トラックバック(0)

トラックバックURL: https://hoge.sub.jp/blog-cgi/mt/mt-tb.cgi/1558

このブログ記事について

このページは、Lyoが2008年4月13日 22:17に書いたブログ記事です。

ひとつ前のブログ記事は「飲み会でした」です。

次のブログ記事は「微妙に新しくなったtumblrで動くようにするためのパッチを適用したReblogCommandにキー操作一発でprivateなreblogもできるような機能を追加した」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

月別 アーカイブ

OpenID対応しています OpenIDについて
Powered by Movable Type 7.9.3