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 のように実装しています。