EffectiveJava読書会2日目

1日目(第2章)の記事はこちら → [EffectiveJava読書会1日目](http://qiita.com/nabuchi@github/items/087803bda368bdb29a19)

今回は **第3章「すべてのオブジェクトに共通のメソッド」** についてやります。

# 項目8 equalsをオーバーライドする時は一般契約に従う

## equalsをオーバーライドすべきとき、すべきでないとき

こんなクラスでequalsをオーバーライドするのは嫌だ（というか、必要ない）

1. クラスのインスタンスが本質的に一意
2. 論理的に同じかどうかを調べる必要がない
3. スーパークラスでオーバーライドされているequalsがこのクラスに対しても適切に動く
4. クラスがprivateでequalsが絶対に呼ばれない（→この場合はAssertionErrorなどをthrowすると良い）

じゃあどんなときにオーバーライドするの？
→ 論理的等価性の判定が必要で、スーパークラスにそれを満たすequalsが実装されていない場合

## equalsを実装する時に満たさなければならない「一般的契約」

Objectの仕様によると、equalsメソッドは以下の5点を厳守しなければならない。

（x,y,zはnullではないとする）
1. **反射的**
　```x.equals(x)```が必ずtrue
2. **対称的**
　```y.equals(x)```がtrueの時のみ、```x.equals(y)```は必ずtrue
3. **推移的**
　```x.equals(y)```と```y.equals(z)```がtrueのとき、```x.equals(z)```は必ずtrue
4. **整合的**
　equalsの判定で使う情報に変更がなければ、```x.equals(y)```は常にtrueを返すか、常にfalseを返すかのどちらか
5. **非null性**
　```x.equals(null)```は必ずfalse

自分とは異なるクラスからequalsが呼ばれることも頻繁にあるし、数多くのクラスがこの「契約」のもとに実装されている。これらを守らないと挙動がおかしくなったり、クラッシュしたりすることがある。

##質の高いequalsメソッドを実装するために

1. 引数が自分自身のオブジェクトへの参照であるかを検査するのに==演算子を使う
2. 引数が正しい型かを検査するのにinstanceof演算子を使う
3. 引数を正しい型にキャストする
4. クラスの各「有意味な」フィールドに対して、引数のオブジェクトと自分自身のフィールドが一致するか調べる
　ⅰ **基本データ型のフィールド：**==演算子を利用
　ⅱ **Float/Double：**Float.compare/Double.compareを利用
　ⅲ **配列：** 各要素ごとに判定　（ずべて比較するならArrays.equalsが使える）
　ⅳ 参照の比較では正当なnullを比較することもあるが、その際NullPointerExceptionに気をつける

5. equalsメソッドを実装したとき、equalsが「対照的・推移的・整合的」であるかを再度確認する

# 項目9 equalsをオーバーライドする時は、常にhashCodeをオーバーライドする

##なぜhashCodeをオーバーライドする必要が？
equalsをオーバーライドしているときにhashCodeをオーバーライドしないと、Object.hashCodeの「一般的契約」を破ることになり、ハッシュに基づくコレクション（HashMap、HashSet、HashTableなど）が正常に動作しなくなる

##hashCodeが満たすべき「契約」
1. equalsの比較で使う情報が変更されない場合、ひとつのオブジェクトのhashCodeが何度呼ばれても（同一アプリケーションの一回の実行において）同じ整数値を返す
2. 2つのオブジェクトがequalsで等しい場合、2つのオブジェクトのhashCodeは同じ整数値を返す
3. 2つのオブジェクトがequalsで等しくない場合に別のhashCodeを返す必要はないが、同じhashCodeを返すような場合が多い場合、ハッシュテーブルのパフォーマンスの低下が起きる可能性がある

equalsを書き換えたときに「契約違反」となりやすいのは
**「2. 2つのオブジェクトがequalsで等しい場合、2つのオブジェクトのhashCodeは同じ整数値を返す」** 
つまりequalsが等しくてもhashCode上では別オブジェクトと認識されてしまう

## じゃあhashCodeをどう実装する？

適切なハッシュ関数を定義するのは難しい

**最悪の「正当なハッシュ関数」**

```java 
@Override public int hashCode() { return 42; } // 生命・宇宙・すべての答え!!!
```

すべてのインスタンスで同じ値を返すため、確かに上の3つの契約は満たすが、ハッシュテーブルが威力を発揮しなくなる（リンクリストと同じに……）

**よくあるハッシュ関数の実装**

1．``` result = (奇数の素数a) ```で初期化
2. equalsの比較で利用しているフィールド **「のみ」** 各フィールドごとに以下を計算
　2-1. フィールドの情報を使ってintな```(ハッシュ値c)```を計算
　2-2. ```result = (奇数の素数b) * result + (ハッシュ値c)```

**各データ型ごとのハッシュ値の計算方法**
boolean: ```(field ? 0 : 1)```
byte, char, short, int: ```(int)field```
long: ```(int)(field ^ (field >>> 32))```
float: ```Float.floatToIntBits(field)```
double: ```Double.doubleToLongBits(field)``` →得られたlongを上記の方法でハッシュ計算
オブジェクト参照：フィールドのequalの再帰呼び出しでequalsが実装されている場合は再帰的にhashCodeを呼び出す
配列：各要素ごとにハッシュ値を求める

# 項目10 toStringを常にオーバーライドする

## 有用なtoStringとは？

toStringはデバッガの出力や、printlnにオブジェクトが渡されたときなどに呼び出される
その時に、その **オブジェクトについての有用な情報（中身）がわかりやすく表示されるとうれしい**

デフォルトのObjectのtoStringで表示される情報はこんな感じ

```
(クラス名)@(hashCodeの値)
```

これじゃあ内容がぜんぜんわからない……
だから、toStringは常にオーバーライドしてオブジェクトの中身が適切に表示されるようにする

## toStringでの表示形式の意図をしっかりドキュメントに書くべし！

一度toStringの戻り値の表示形式を決めてしまうと、それ以降その形式を変更は実質不可能

# 項目11 cloneを注意してオーバーライドする

## cloneの満たすべき「契約」

java.lang.Objectの仕様をまとめると...

・ cloneは **オブジェクトのコピーを生成して返す**（＝新たなインスタンスを生成する）
・ オブジェクト内部のデータ構造もコピーされる必要があるかもしれない

・ **コンストラクタは呼び出されない**

大まかなには ```x.clone() != x``` とか ```x.clone().getClass() == x.getlass()``` とか ```x.clone().equals(x)``` などがtrueになるということ（ただし、必須事項ではない）

## 正しく機能するcloneを実装するときの方針

**基本方針（すべてのフィールドが基本データ型or不変オブジェクトのとき）**
・スーパークラスのcloneメソッド(super.clone())を呼び出して、自分のクラスにキャストして返す
**可変オブジェクトを参照しているフィールドを持つとき**
・クラス中で参照しているフィールドのcloneメソッドも再帰的に呼び出す
**ハッシュテーブルのclone**
・ハッシュテーブルを構成するバケット配列の各バケット（リンクリスト）をdeepCopyする


その他の注意点
・ スレッドセーフなクラスにcloneを実装するときはcloneも適切に同期させなければならない
 

## そこまでしてcloneを実装する必要があるの？
・Cloneableを実装しているクラスを拡張する場合は必須
・コピーコンストラクタ・コピーファクトリーを使うほうが懸命な場合が多い
・オブジェクトコピーをする必要がないケースもある（不変なオブジェクトのコピー機能は基本的に不要）


# 項目12 Comparableの実装を検討する

compareToをちゃんと実装すると、各インスタンスの自然な順序を定義できる
(→ソート済みのデータ構造(TreeSet, TreeMap)や検索・ソートができる(Collections, Arrays)などが正常に動作するようになる)

## compareToの「一般契約」

1. すべてのx,yについて、```sgn(x.compareTo(y)) == -sgn(y.compareTo(x))```
(y.compareTo(x)が例外を投げるときだけx.compareTo(y)が例外を投げる)

2. 推移的である　x.compareTo(y) > 0, y.compareTo(z) > 0 ならば x.compareto(z) > 0
3. すべてのzについて、```x.compareTo(y)==0```のとき
```sgn(x.compareTo(z)) == sgn(y.compareTo(z))```
4. ```(x.compareTo(y) == 0)　== (x.equals(y))```が強く推奨されるが厳密には必須ではない
(従っていない場合はちゃんとそれを明記する)

## compareToの比較とequalsの比較

comparetoは「順序比較」、equalsは「同値比較」

compareToで比較する2つのインスタンスの型が違う場合はClassCastExceptionをスローする
（equalsでは異なるクラス同士で比較することもある）

## compareToを実装するときの基本方針

**基本フィールドの順序比較**
関係演算子「＞」か「＜」で比較する
**Float・Doubleの比較**
Float.compareやDouble.compareを使って比較する
**複数のフィールドを持つクラスの比較**
各フィールドの重要性を考えて、一番重要なフィールドから比較して順序を決める　（同じなら次に重要なフィールドで比較）