minimize

equals と hashCode

Objectクラスに定義されている数少ないメソッドの中で、
これら二つはオーバーロード(上書き)する機会がたまにあります。

ハッシュマップのキーに使うクラスには、必ずこれを実装する必要があります。
ここで大事なことは、「これらのメソッド両方をオーバーロードしなければならない」
という点です。

PMD : OverrideBothEqualsAndHashcode
CheckStyle : EqualsHashCode

hashCodeの実装方法

ハッシュコードは、オブジェクトの内容が変化しない限り
同じ値を取ります。
従って、一度計算したハッシュコードはオブジェクト内に保持して
次回の同メソッド呼出時にはそれを返すようにします。

ハッシュマップは頻繁にこのメソッドを呼び出すので、
毎回ハッシュコードを計算していたのではパフォーマンスが落ちるのです。

オブジェクトの内容が変化したときには、保持しているハッシュコードをクリアします。
その為、ハッシュコードが計算されていない状態が 0 を示すようにします。

equals実装時の注意

例えば Foo クラスに equals メソッドを実装するとき、

public boolean equals(Foo o) {
  ...
}

ついこのようなコードを書いてしまいがちですが、これは間違っています。
正しくは、

public boolean equals(Object o) {
  ...
}

です。equals メソッドの引数は必ず Object 型にしないといけません。
そうしないと、Object.equals() をオーバーライドすることにはならず
全く関係の無い equals メソッドが定義されただけだと認識されてしまいます。

CheckStyle : CovariantEquals
FindBugs : Eq: Covariant equals() method defined

BigDecimalの生成

BigDecimalにはdouble値を引数に取るコンストラクタがありますが
これを使ってはいけません。

new BigDecimal(4.15); -> new BigDecimal("4.15");

前者の方では、精度が失われる可能性があります。

PMD : AvoidDecimalLiteralsInBigDecimalConstructor

super() の呼び出し

Javaでは、コンストラクタ内で自動的に super() が呼び出されます。

public class A {
  public A() {
    ...
  }
  public A(int n) {
    ...
  }
}

public class B extends A {
  public B() {
    // 何も記述しなくても、A() の処理が実行される
  }
  public B(int n) {
    // 何も記述しなくても、A() の処理が実行される
    // しかし、実際には A(int) の処理を実行させたいはず
  }
}

デフォルトの処理に頼っていると、オーバーロードしたコンストラクタ内で
ミスが発生しやすくなります。
ですので、明示的に super() と記述しておくことが
良いコーディングスタイルとされているようです。

PMD : CallSuperInConstructor

アクセス識別子について

Javaではメンバのスコープをいくつかの段階で定義できます。

このうち、パッケージprivateはそのスコープ範囲が
明確でないという理由であまり推奨されていません。
また、フィールドを protected にするのも同様に勧められません。

PMD : DefaultPackage
CheckStyle : VisibilityModifier

デフォルト(無名)パッケージについて

package宣言をしないクラスは、デフォルトパッケージに所属することになります。
これは一時的なテスト用にはいいかもしれませんが
一般的にはあまり使わない方がいいとされています。

PMD : DefaultPackage
CheckStyle : PackageDeclaration

オブジェクトの比較

数値などのプリミティブ型の比較には == 演算子を使いますが
オブジェクトの比較には通常 equals() を使います。
これらがどのように違うかというのは、なかなか説明するのが難しいのですが
例を挙げて簡単に説明します。

int x = 10;
int y = 10;
if (x == y) {
  // 当然 x == y であり、真となる
}
Car car1 = new Car("Type-A");
Car car2 = new Car("Type-A");
if (car1 == car2) {
  // car1, car2 ともに同じ内容であるが、真にはならない
}
if (car1.equals(car2)) {
  // car1 と car2 は共にType-Aであり、同値なので真になる
}

このように、オブジェクトの場合
両者が「同一」である事と「同値」である事を区別する必要があります。
同一である場合に限って == 演算子は真を返します。
equals() は、同一でなくても同値ならば真を返します。
見ての通り equals() はメソッドであり、オーバーライド可能な為
「同値」である判断基準はプログラムレベルで変更することが出来ます。
それに対し「同一」である判断基準は変更することが不可能です。

ほとんどの場合、オブジェクトの同一性を判定する場面はありません。
よって、オブジェクトの比較には == ではなく equals() を使います。
さらに言えば、Object.equals() の実装は

public boolean equals(Object o) {
  return this == o;
}

となっていますので、オブジェクトの同一性を判定したい場合は
equals() メソッドをオーバーライドせずに呼び出せば良いという事になるので
全ての場面において == 演算子を使う必要が無いという事です。

また、a.equals(b) という処理において、どちらをaにしてもbにしてもいいのですが
もし定数値があったらそれを迷わずaにしましょう。
a が null である可能性がある場合には、事前に null チェックをする必要があります。

PMD : CompareObjectsWithEquals / PositionLiteralsFirstInComparisons
CheckStyle : StringLiteralEquality
FindBugs : ES: Comparison of String objects using == or != / RC: Suspicious reference comparison

旧式のクラス

Vector や Hashtable は、JDK1.2以前で使われていたクラスで
現在はその使用が奨められていません。
代わりに ArrayList や HashMap を使用しましょう。

CheckStyle : IllegalType

配列の比較

配列の比較には equals() メソッドが使えません。
代わりに java.util.Arrays.equals(Object[], Object[]) を使用します。

FindBugs : EC: Invocation of equals() on an array,which is equivalent to ==

シリアライズ

シリアライズ可能なクラスには、非直列化可能なフィールドを定義してはいけません。
シリアライズしようとした段階で例外が発生してしまいます。

class A implements Serializable {
  private Iterator it;
  // Iteratorは非直列化フィールドなので、正しくシリアライズされない可能性がある。
}

この場合の対処法としては、非直列化フィールドをtransientにするか
独自の read/writeObject メソッドを実装する方法があります。

FindBugs : Se: Non-transient non-serializable instance field in serializable class
FindBugs : Se: Class is Serializable but its superclass doesn't define a void constructor

デフォルトコンストラクタについて

Javaでは、クラスにコンストラクタを定義していない場合に
引数無しのデフォルトコンストラクタが暗黙的に作成されます。

public class A {
  public void bar() {
    A obj = new A();
    ...
  }
}

このような場合、一つコンストラクタを作成すると
デフォルトコンストラクタは「作成されません」。
つまり…

public class A {
  public A(int v) {
    ...
  }
  public void bar() {
    A obj = new A(); // コンパイルエラー
    ...
  }
}

こうなってしまいます。
ですから、デフォルトコンストラクタに頼ったコーディングというのは推奨されません。

PMD : AtLeastOneConstructor
CheckStyle : MissingCtor

[コメント(0)]