Java中級者以上の必須本である、Effective Java 第3版に Kindle版が出たので、まとめる。
前:Effective Java 第3版 第2章オブジェクトの生成と消滅
次:Effective Java 第3版 第4章クラスとインタフェース
項目10 equalsをオーバーライドするときは一般契約に従う
- equalsを間違ったやり方でオーバーライドしてしまうと、その結果は悲惨なものになる。
- 問題を避ける最も簡単な方法は、オーバーライドしないこと。
次の条件のどれかに当てはなるなら、オーバーライドしないのが正しいやり方。
- クラスの個々のインスタンスが本質的に一意である。
- クラスが「論理的等価性」の検査を提供する必要がない。
- スーパークラスがすでにequalsをオーバーライドしており、スーパークラスの振る舞いがこのクラスに対して適切である、
- クラスがprivateあるいはパッケージプライベートであり、そのequalsメソッドが呼び出されないことが確かである。
equalsメソッドは同値関係を実装し、以下の性質を持つ
- 反射的: nullではに任意の参照値xに対して、
x.equals(x)
はtrueを返さなければなりません。 - 対照的: nullではない任意の参照値xとyに対して、
y.equals(x)
がtrueを返す場合のみ、x.equals(y)
はtrueを返さなければなりません。 - 推移的: nullでない任意の参照値x、y、zに対して、もし、
x.equals(y)
とy.equals(z)
がtrueを返すならば、x.equals(z)
はtrueを返さなければなりません。 - 整合的: nullでない任意の参照値x、yに対して、
x.equals(y)
の複数回の呼び出しは、equalsの比較で使われる情報に変更がなければ、一貫してtrueを返すか、一貫してfalseを返さなければなりません。 - nullではない任意の参照値xに対して、
x.equals(null)
はfalseを返さなければなりません。
[Good]equalのオーバーライド例
public final class PhoneNumber {
private final short areaCode, prefix, lineNum;
public PhoneNumber(int areaCode, int prefix, int lineNum) {
this.areaCode = rangeCheck(areaCode, 999, "area code");
this.prefix = rangeCheck(prefix, 999, "prefix");
this.lineNum = rangeCheck(lineNum, 9999, "line num");
}
private static short rangeCheck(int val, int max, String arg) {
if (val < 0 || val > max) {
throw new IllegalArgumentException(arg + ": " + val);
}
return (short) val;
}
// 【良い例】 equalのオーバーライド例
@Override
public boolean equals(Object o) {
if (o == this) {
// 自分自身のオブジェクトへの参照であるか?
return true;
}
if (!(o instanceof PhoneNumber)) {
// 正しい型であるか?
return false;
}
// 正しい型でキャストする
PhoneNumber pn = (PhoneNumber) o;
// 意味のあるフォールドのそれぞれについて一致するか?
return pn.lineNum == lineNum && pn.prefix == prefix && pn.areaCode == areaCode;
}
}
IntelliJで自動生成されるequalsメソッド
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PhoneNumber that = (PhoneNumber) o;
return areaCode == that.areaCode &&
prefix == that.prefix &&
lineNum == that.lineNum;
}
- GoogleのAutoValueフレームワークを使うことで、アノテーションによるequalsとhashCodeの自動生成をしてくれる。
項目11 equalsをオーバーライドするときは常にhashCodeをオーバーライドする
hashメソッドの契約
- アプリケーション実行中に同じオブジェクトにhashCodeメソッドが繰り返し呼び出された場合、equal比較で使われるオブジェクトの情報に変更がなければ、hashCodeメソッドは常に一貫して同じ値を返さなければなりません。この値は、同じアプリケーションの一回の実行と別の実行で一致している必要はありません。
- 二つのオブジェクトがequals(Obeject)メソッドにより等しければ、二つのオブジェクトそれぞれに対するhashCodeメソッド呼び出しは、同じ整数の結果を生成しなければなりません。
- 二つのオブジェクトがequals(Object)メソッドにより等しくなければ、二つのオブジェクトそれぞれに対するhashCodeメソッド呼び出しが、異なる整数の結果を生成しなければならないことは要求されていません。しかし、等しくないオブジェクトに対して異なる整数の結果を生成することは、ハッシュテーブルのパフォーマンスを改善するかもしれないことをプログラマは認識しておくべきです。
hashCodeのオーバーライド例
// 【良い例】hashCodeのオーバーライド例
@Override
public int hashCode() {
int result = Short.hashCode(areaCode);
result = 31 * result + Short.hashCode(prefix);
result = 31 * result + Short.hashCode(lineNum);
return result;
}
-
31
は、奇数の素数であり、パフォーマンスが良いので、伝統的に選ばれる。 - GuavaのHashingは、衝突が少ない。
- 以下のhashCodeは、ボクシング/アンボクシングを行うので、パフォーマンスはあまり良くない。
IntelliJで自動生成されるhashCodeメソッド
@Override
public int hashCode() {
return Objects.hash(areaCode, prefix, lineNum);
}
項目12 toStringを常にオーバーライドする
- 作成する全てのインスタンス化可能なクラスでは、スーパークラスがすでにオーバーライドしていないのなら、ObjectのtoStringの実装をオーバーライドする。
- それにより、クラスは使いやすくなり、デバッグに役立つ。
- toStringメソッドは、美的で優れた形式で、オブジェクトの簡潔で役立つ説明を返すべき。
項目13 cloneを注意してオーバーライドする
- Cloneableインタフェースは、クラスは複製可能を許可していることを示している。
- Objectのcloneメソッドはprotectedだが、Cloneableを実装しているクラスは適切に機能するpublicのcloneメソッドを提供することが期待されている。
- 不変クラスは無駄な複製を作ってしまうため、cloneを提供すべきではない。
可変な状態への参照を持たないクラスのcloneメソッド
// Cloneableを実装する
public final class PhoneNumber implements Cloneable {
// 省略
@Override
public PhoneNumber clone() {
try {
return (PhoneNumber) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(); // 起こりえない
}
}
可変な状態への参照を持つクラスのcloneメソッド
public class Stack implements Cloneable {
// 省略
@Override
public Stack clone() {
try {
Stack result = (Stack) super.clone();
// 配列をcloneしてメンバ変数に入れる。
result.elements = elements.clone();
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
項目14 Comparableの実装を検討する
- 自然な順序を持っていることを示す、Comparableインタフェースは、唯一のメソッドcompareToを持っている。
コンパレータ構築メソッドを持つComparable
public final class PhoneNumber implements Comparable<PhoneNumber> {
// 省略
private static final Comparator<PhoneNumber> COMPARATOR =
Comparator.comparingInt((PhoneNumber pn) -> pn.areaCode)
.thenComparingInt(pn -> pn.prefix)
.thenComparingInt(pn -> pn.lineNum);
@Override
public int compareTo(PhoneNumber pn) {
return COMPARATOR.compare(this, pn);
}