Effective Java 3rd [Chapter 3] - すべてのオブジェクトに共通のメソッド
すべてのオブジェクトに共通のメソッド
Effective Java 第 3 版の個人的メモ
- 項目 10 equals の override は一般契約(general contracts)に従うべし
- 項目 11 equals をオーバーライドする場合は hashcode もオーバーライドせよ
- 項目 12 常に toString をオーバーライドせよ
- 項目 13 clone をオーバーライドするときは注意せよ
- 項目 14 Comparable を実装することを考慮せよ
1. 項目 10 equals の override は一般契約(general contracts)に従うべし
1.1. どういう時にオーバーライドする必要があるのか?
クラスが単なるオブジェクトの同一性とは異なる論理的等価性という概念を持っていて、他のインスタンスと比較する必要があるとき
ただし、スーパークラスが equals をオーバーライドしていないこと。
1.2. どうやってオーバーライドすれば良いのか?
一般契約(反射性, 対照性, 推移性, 整合性, 非 null 性)に従うこと
以下のツールを使うと簡単に一般契約に従ったコードにできる。
- Annotation Processor(Java 6 から追加)
lombokの@Dataなど- 欠点:Annotation Processor を 2 つ利用していて、
@Dataとは別の Annotation がsetter()を要求している場合、コンパイルエラーとなる場合がある。@Dataが後で処理されるようになっていると、setter()がないからである。この場合はどうしようもない。。
Annotation Processor は、コンパイル時に処理される。
lombokの@Dataの場合は、コンパイル時に、setter/getter/equals/hashCode/toStringメソッドのバイトコードを.classに突っ込んでくれる。
- Eclipse や Intellij の自動生成機能
- GUI で簡単に生成可能
- 欠点:メンバ変数を追加した際には、その都度、GUI で実行しないといけないから手間である。
2. 項目 11 equals をオーバーライドする場合は hashcode もオーバーライドせよ
2.1. hashcode もオーバーライドしないとどうなるのか?
論理的に等しいが、hashCodeの値は異なることになってしまう。
論理的に等しいのに異なってしまうのはおかしいので、hashCodeも同じであるべきである。
- 論理的に等しい(フィールド値が同じである)ので、その値を使った Hash 関数を作成しましょう。
こちらも以下のツールを使いましょう
-
Annotation Processor(Java 6 から追加)
-
Eclipse や Intellij の自動生成機能
PhoneNumber.java
|
|
2.2. 【余談】volatile 修飾子について
項番 78「共有される mutable なデータには、同期したアクセスをすべし」で説明されるらしいですが、、この章に出てきたので。
2.2.1. volatile 修飾子とは?
volatile修飾子をつけると、「必ずメモリに書く」ようになる。
並列処理などで、
あるインスタンスAのメンバ変数Xを更新する際、通常はスレッドキャッシュに書き込まれた後、メモリに書き込まれる。 これをスレッドキャッシュへの書き込みをせず、メモリ書き込みを最初からさせるようにするのが、volatile修飾子である。
ただし、「volatile で排他制御出来ると、いつから思ってた? atomic や synchronized 使うべし」に記載があるように、volatile修飾子では排他制御はできない。。
そのため、結局は、atomicやsynchronizedを使って、排他制御することになる。
2.2.2. volatile修飾子の使い所について
項番 78 を見たところ、以下の double-checked locking の話が出ているが、double-checked locking と Singleton パターンを読んだところ、結局、volatile修飾子は使えず、同期化を受け入れるか、static field を使うようである、、、つまりvolatile修飾子は使われないよう、、、いつ使うねん。。笑
Singleton.java
|
|
項番 78「共有される mutable なデータには、同期したアクセスをすべし」まで、とりあえずは放置しておくことにする。
3. 項目 12 常に toString をオーバーライドせよ
3.1 まとめ
- toString には、フィールド値を含んだ情報を提示しましょう。
- Eclipse や Intellij で
toString()を自動生成できる。
- Eclipse や Intellij で
ToStringSample.java
|
|
-
BigIntegerやBigDecimalなどのように数値を扱うクラスは、toString(), valueOf()で相互変換できるようにしておきましょう。- その方が使いやすくて便利。
-
toString()以外でもフィールド値などの情報を得られるようにしておきましょう。- 利用者側が、無理やり
toStringから値を取得するようなことになってしまうので、、、
- 利用者側が、無理やり
4. 項目 13 clone をオーバーライドするときは注意せよ
4.1 はじめに
-
Cloneable インタフェースはオブジェクトが複製を許可していることを示すだけの空のインターフェースだよ。
-
java.lang.Object の clone メソッドは protected なので、Cloneable インタフェースを実装するだけでは clone メソッドを呼び出すことができないよ。
-
可変オブジェクトの参照がある場合は、参照先を「
Deep Copy」しましょう。Deep Copyは配列やリストの値を 1 つずつコピーする方法で、対するShallow Copyはアドレスを単にコピーするだけだよ。
4.2 結論
-
純粋な Java の世界では、
cloneメソッドを使わずに、copyFactoryメソッドで複製できるように作る方法がいいよ。 -
Spring の世界では、
ApplicationContextがインスタンスを管理しているので、ApplicationContenxt#getBean()で、Prototypeとしてインスタンスを取得する方法がいいよ。- spring のシングルトン問題を@Scope を使って回避するのブログの通り、Spring ではシングルトン管理なので、何もしないと
Cloneではなく、そのインスタンスを取得してしまうからですね。
- spring のシングルトン問題を@Scope を使って回避するのブログの通り、Spring ではシングルトン管理なので、何もしないと
5. 項目 14 Comparable を実装することを考慮せよ
5.1 はじめに
-
compareTo()は、Comparable インタフェースの唯一のメソッド -
CompareTo()メソッドを持っているクラスは、「自然な順序関係」(abc 順や数値順、年代順など)を持っていることを表しているよ。- 自然な順序関係の例=辞書順、数値順
- 1,2,3,4,..,9,10,11,…,99,100 をソートする時にどの順序関係で扱うかで結果が変わる
- 数値順=上に記述した通り
- 辞書順=
1,10,100,11,..,2,20,21,...3,30...- Java だと、String で sort すると、辞書順ソートになる。
-
compareTo契約(equals の契約と同様)を破っていると、compareTo()メソッドに依存しているクラス(TreeSet, TreeMap, Collections, Arraysなど)が機能しなくなるので気をつけましょう。
5.2 どのように実装するか
compareToメソッドを実装している2つの例を確認しましょう。
PhoneNumber_1.java
|
|
PhoneNumber_2.java
|
|
- PhoneNumber_1.java はPhoneNumber_2.java と比べて計算量が多くて遅いけれど、確実に動作する。
- PhoneNumber_2.java は PhoneNumber_1.java と比べて計算量が少なくて速いが、最小値と最大値の差が、int の最大値(
2^31 - 1)を超えていたらオーバーフローしてしまう。
5.3 結論
最小値と最大値の差が、int の最大値(2^31 - 1)を超えていたらオーバーフローしてしまう可能性がある、もしくは、数値の比較ではない場合、PhoneNumber_1.java のように実装しましょう。そうでなければ、PhoneNumber_2.java のように実装しましょう。
java 標準のBigDecimal#compareTo()とか、Timestamp#compareTo()は、オーバーフローする可能性がある、もしくは、数値での比較では無いので、PhoneNumber_1.java のように実装しています。
java 標準のByte#compareTo()は、オーバーフローする可能性がないので、PhoneNumber_2.java のように実装しています。