Javaパズル7 equalsメソッドのオーバーライド

Quiz 以下のソースを実行するとどうなりますか?


package main;

import java.sql.Timestamp;
import java.util.Date;

public class TestHoge {

    public static void main(String[] args) {

        long systime = System.currentTimeMillis();

        Date date = new Date(systime);
        Date timestamp = new Timestamp(systime);

        System.out.println("date.equals(timestamp) = " + date.equals(timestamp));
        System.out.println("timestamp.equals(date) = " + timestamp.equals(date));
    }

}


注)java.sql.Timestampクラスは、java.util.Dateを継承しています。









実行すると以下のようになります。

date.equals(timestamp) = true
timestamp.equals(date) = false



数学でイコールの規則として、一般的に以下の規則が知られています。
●1・・・反射性・・・任意のa に対して、a=aが成り立つ。
●2・・・対象性・・・a=bが成り立つならば、b=aが成り立つ。a≠bが成り立つならば、b≠aが成り立つ。
●3・・・推移性・・・a=b、b=cが成り立つならば、a=cが成り立つ。


javaプログラムの世界では、nullという考えがあるので、上記を踏まえequalsメソッドは以下のようになっていないといけないかと思われます。
(注・・・実際には要件によるところもあるかもしれませんので、ここで書いていることは絶対ではありません)
●1・・・反射性・・・nullでない任意のaという参照に対し、a.equals(a)はtrueとなる。
●2・・・対象性・・・nullでないa,bという参照に対し、a.equals(b)がtrueならば、b.equals(a)はtrueとなる。a.equals(b)がfalseならば、a.equals(b)はfalseとなる。
●3・・・推移性・・・nullでないa.b,cという参照に対し、a.equals(b)がtrue、b.equals(c)がtrueならば、a.equals(c)はtrueとなる。
●4・・・nullでないaという参照に対し、a.equals(null)はfalseとなる。
●5・・・a,bが参照するオブジェクトが変更されていない場合、a.equals(b)は常に同一の値を返さないといけない。


さて、問題の回答とみると、2の対象性が成り立っていないことに気づきます。
これは、oracle(javaを開発していたSUNマイクロシステムズはoracleに吸収合併されたのですね。)の公式javaソースのjava.util.Date型、java.sql.timestamp型のequalsメソッドのオーバーライドがヘンテコなので起こる現象です。

java.util.Date型のequalsメソッド
public boolean equals(Object obj) {
    return obj instanceof Date && getTime() == ((Date) obj).getTime();
}

java.sql.Timestamp型のequalsメソッド
public boolean equals(Timestamp ts) {
    if (super.equals(ts)) {
        if  (nanos == ts.nanos) {
            return true;
        } else {
            return false;
        }
    } else {
        return false;
    }
}

public boolean equals(java.lang.Object ts) {
    if (ts instanceof Timestamp) {
        return this.equals((Timestamp)ts);
    } else {
        return false;
}


問題のtimestamp.equals(date)がfalseになるのは、instance演算子でのチェックで、if(date instanceof Timestamp) という形になるからかと思われます。(うまくデバッグできなかったので確信がもてないです。)

javaのソースや、インターネットでequalsメソッドのオーバーライドで検索すると、instanceofだけで済ます実装方法をよく見かけますが、equalsメソッドの対象性をチェックしているのかどうか怪しいです。
equalsメソッドのオーバーライドして、さらにサブクラスを作るってことは業務であまり行われていないのか、問題となることがあまりないのかな?


では、5つの条件を満たすequalsメソッドの実装方法は以下のようになります。


 package bean;

import java.util.Arrays;

public class HogeBean {

    private String[] hogeStrAr;

    public String[] getHogeStrAr() {
        if (hogeStrAr == null) {
            return null;
        }
        return (String[]) hogeStrAr.clone();

    }

    public void setHogeStrAr(String[] hogeStrAr) {
        if (hogeStrAr == null) {
            this.hogeStrAr = null;
        } else {
            this.hogeStrAr = (String[]) hogeStrAr.clone();
        }
    }

    @Override
    public String toString() {
        return "HogeBean [hogeStrAr=" + Arrays.toString(hogeStrAr) + "]";
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + Arrays.hashCode(hogeStrAr);
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj){         //ポイント1
            return true;
        }
        if (obj == null){         //ポイント2
            return false;
        }
        if (getClass() != obj.getClass()){   //ポイント3
            return false;
        }
        HogeBean other = (HogeBean) obj;
        if (!Arrays.equals(hogeStrAr, other.hogeStrAr)){   //ポイント4
            return false;
        }
        return true;
    }

}


ポイント1:==が成り立つということは、参照アドレスが同一なので、完全に一致するはずなのでtrueを返す。最適化としての意味もあります。

ポイント2:obj==nullならばfalseを返すのはequalsメソッドの規則●4番のチェックです。また、ポイント3でNullPointerExceptionが発生しないようにチェックを入れている意味もあります。

ポイント3:クラスが一致しない場合はfalseを返すようにします。equalsメソッドの規則●2番を満たすために必要です。instanceofではダメなのは記述済み。

ポイント4:フィールドの同一かどうかチェックします。基本型のフィールドは==で、オブジェクト型はequalsでチェックします。


サブクラスをequalsメソッドのオーバーライドするには以下のようにします。

package bean;


public class HogeBeanChild extends HogeBean{

    private long hogeLong;

    public long getHogeLong() {
        return hogeLong;
    }

    public void setHogeLong(long hogeLong) {
        this.hogeLong = hogeLong;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = super.hashCode();
        result = prime * result + (int) (hogeLong ^ (hogeLong >>> 32));
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!super.equals(obj)) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        HogeBeanChild other = (HogeBeanChild) obj;
        if (hogeLong != other.hogeLong) {
            return false;
        }
        return true;
    }

}


オブジェクトがnullかどうかの判定のところをsuperクラスで行うようにするだけです。
なお、eqlipseで開発している場合、
右クリック→ソース→hashCode()及びequals()の生成
でequalsメソッド作れますので、心配無用です。


参考文献
コアJavaVol1(古い本ですけど、それなりに参考になります。)

コアJava2〈Vol.1〉基礎編 (サンソフトプレスシリーズ)
ケイ・S. ホーストマン ゲイリー コーネル
アスキー
売り上げランキング: 378394



また、以下のように、equalsメソッドを毎回オーバーライドしない方法というのがあるみたいです。
http://d.hatena.ne.jp/wistery_k/20120110/1326183727
関連記事


--------------------------------------------------------------------------------------

コメントの投稿

非公開コメント

このブログについて
  • 全記事一覧(時間順)
  • このブログについて
  • 私のプロフィール
  • 当ブログで扱っている動画について
  • 記事違いなコメントのお返事

  • カテゴリー
    twitter
    カレンダー
    04 | 2017/05 | 06
    - 1 2 3 4 5 6
    7 8 9 10 11 12 13
    14 15 16 17 18 19 20
    21 22 23 24 25 26 27
    28 29 30 31 - - -
    Amazon
    でたらめな当ブログにぴったりな商品を自動で表示するみたいです。



    月別アーカイブ
    プロフィール

    たづみ

    Author:たづみ
    ・1981年生まれの男
    ・もう少し詳細なプロフィールはこちらで

    最新コメント
    アクセスランキング
    [ジャンルランキング]
    日記
    1035位
    アクセスランキングを見る>>

    [サブジャンルランキング]
    会社員・OL
    212位
    アクセスランキングを見る>>