java重寫hashcode方法那點事

今天在調用別人寫的對象時發現一個很奇怪的問題,首先描述下需求:

簡要需求:從數據庫查詢一個對象Dish,將這個Dish上傳到某某平臺,過一點時間後,某某平臺會將該Dish對象傳回來,我們需要判斷這個對象在數據庫是否有被修改的痕跡;我們首先想到的就是第一次從數據庫查詢出這個對象的時候,計算出其hashcode值一併傳給某某平臺,然後當某某平臺將這個hashcode再次傳回來的時候,我們再從數據庫查詢同樣的對象Dish並計算出其hashcode1,如果hashcode= hashcode1,我們認爲該對象在此期間在數據庫中沒有被修改;

需求不重要,簡單說下即可,我們主要看怎麼重寫Dish對象的hashcode的方法的:

Dish類結構:

import java.util.List;

public class Dish {

    private String dishCode;
    
    private boolean weighing;

    private String categoryCode;

    private boolean currentPrice;

    private boolean discountable;

    private boolean stopSell;

    private boolean soldOut;

    private boolean deal = false;

    private List<DealGroup> dealGroupList;

    public String getDishCode() {
        return dishCode;
    }

    public void setDishCode(String dishCode) {
        this.dishCode = dishCode;
    }

    public boolean isWeighing() {
        return weighing;
    }

    public void setWeighing(boolean weighing) {
        this.weighing = weighing;
    }

    public String getCategoryCode() {
        return categoryCode;
    }

    public void setCategoryCode(String categoryCode) {
        this.categoryCode = categoryCode;
    }

    public boolean isCurrentPrice() {
        return currentPrice;
    }

    public void setCurrentPrice(boolean currentPrice) {
        this.currentPrice = currentPrice;
    }

    public boolean isDiscountable() {
        return discountable;
    }

    public void setDiscountable(boolean discountable) {
        this.discountable = discountable;
    }

    public boolean isStopSell() {
        return stopSell;
    }

    public void setStopSell(boolean stopSell) {
        this.stopSell = stopSell;
    }

    public boolean isSoldOut() {
        return soldOut;
    }

    public void setSoldOut(boolean soldOut) {
        this.soldOut = soldOut;
    }

    public boolean isDeal() {
        return deal;
    }

    public void setDeal(boolean deal) {
        this.deal = deal;
    }

    public List<DealGroup> getDealGroupList() {
        return dealGroupList;
    }

    public void setDealGroupList(List<DealGroup> dealGroupList) {
        this.dealGroupList = dealGroupList;
    }


    @Override
    public int hashCode() {
        int result = dishCode != null ? dishCode.hashCode() : 0;
        result = 31 * result + (soldOut ? 1 : 0);
        result = 31 * result + (stopSell ? 1 : 0);
        result = 31 * result + (currentPrice ? 1 : 0);
        result = 31 * result + (weighing ? 1 : 0);
        result = 31 * result + (categoryCode != null ? categoryCode.hashCode() : 0);
        result = 31 * result + (dealGroupList != null ? dealGroupList.hashCode() : 0);
        return result;
    }

}
其重寫了hashcode方法,我們知道如果重寫hashcode方法是用到了對象類型,那麼該對象類型也必須要重寫hashcode方法,否則每次得到的hashcode值不一定一致,那麼重寫hashcode方法的意義就不大了;我們發現這裏面用到了dealGroupList這個對象,所有我們必須要重寫這個對象裏面元素的hashcode方法,即DealGroup對象:

DealGroup類結構:

import java.math.BigDecimal;

public class DealGroup {

    /**
     * 是否可選 (必填) 枚舉類型
     */
    private OptionalTypeEnum optionalType;

    private boolean repeatable;

    private BigDecimal minChooseNum;

    private BigDecimal maxChooseNum;

    private boolean hasExtraPrice = false;

    public OptionalTypeEnum getOptionalType() {
        return optionalType;
    }

    public void setOptionalType(OptionalTypeEnum optionalType) {
        this.optionalType = optionalType;
    }

    public boolean isRepeatable() {
        return repeatable;
    }

    public void setRepeatable(boolean repeatable) {
        this.repeatable = repeatable;
    }

    public BigDecimal getMinChooseNum() {
        return minChooseNum;
    }

    public void setMinChooseNum(BigDecimal minChooseNum) {
        this.minChooseNum = minChooseNum;
    }

    public BigDecimal getMaxChooseNum() {
        return maxChooseNum;
    }

    public void setMaxChooseNum(BigDecimal maxChooseNum) {
        this.maxChooseNum = maxChooseNum;
    }

    public boolean isHasExtraPrice() {
        return hasExtraPrice;
    }

    public void setHasExtraPrice(boolean hasExtraPrice) {
        this.hasExtraPrice = hasExtraPrice;
    }

    @Override
    public int hashCode() {
        int result = optionalType != null ? optionalType.hashCode() : 0;
        result = 31 * result + (minChooseNum != null ? minChooseNum.hashCode() : 0);
        result = 31 * result + (maxChooseNum != null ? maxChooseNum.hashCode() : 0);
        return result;
    }
}

到這裏,對象寫完了,開始描述問題。

問題描繪:查詢同樣的一個對象,發現其hashcode值有時候不一致,分析重寫的hashcode方法,發現可能是DealGroup對象重寫hashcode方法時用的的枚舉類型optionalType有問題,因爲枚舉是個對象,且這個枚舉類沒有重寫hashcode方法(我把hashcode方法裏面的optionalType去掉就ok了);然後我就去找對應的開發,讓他看看這個問題,他感覺可能是有問題,就本地測試了幾遍,居然發現他這邊沒有問題,即他得到的hashcode值,永遠都是一樣,不像我有時候不一樣,這我就鬱悶了。

最終結論:

猜測結論,只是猜測,也希望大家能去幫我驗證:我用的jdk7,同事用的jdk8,其他我倆的環境完全一樣,所以猜測是這個原因,可能java8在這塊做了某些修改。

好吧,我承認這個例子沒啥意義,我們聊聊如何重寫hashcode方法;

1、String對象和Bigdecimal對象已經重寫了hashcode方法,這些類型的值可以直接用於重寫hashcode方法;

2、result = 31 *result + (dishCode !=null ?dishCode.hashCode() : 0);,這裏面爲啥用個31來計算,而且很多人都是這麼寫的,這是因爲31是個神奇的數字,任何數n*31都可以被jvm優化爲(n<<5)-n,移位和減法的操作效率比乘法的操作效率高很多?


這邊再拷貝下別人說的比較經典的總結:

Google首席Java架構師Joshua Bloch在他的著作《Effective Java》中提出了一種簡單通用的hashCode算法

1. 初始化一個整形變量,爲此變量賦予一個非零的常數值,比如int result = 17;

2. 選取equals方法中用於比較的所有域,然後針對每個域的屬性進行計算:

  (1) 如果是boolean值,則計算f ? 1:0

  (2) 如果是byte\char\short\int,則計算(int)f

  (3) 如果是long值,則計算(int)(f ^ (f >>> 32))

  (4) 如果是float值,則計算Float.floatToIntBits(f)

  (5) 如果是double值,則計算Double.doubleToLongBits(f),然後返回的結果是long,再用規則(3)去處理long,得到int

  (6)如果是對象應用,如果equals方法中採取遞歸調用的比較方式,那麼hashCode中同樣採取遞歸調用hashCode的方式。  否則需要爲這個域計算一個範式,比如當這個域的值爲null的時候,那麼hashCode值爲0

  (7)如果是數組,那麼需要爲每個元素當做單獨的域來處理。如果你使用的是1.5及以上版本的JDK,那麼沒必要自己去    重新遍歷一遍數組,java.util.Arrays.hashCode方法包含了8種基本類型數組和引用數組的hashCode計算,算法同上,


關於hashcode,我們一定要知道一個口訣:

1、hashcode相等,兩個對象不一定相等,需要通過equals方法進一步判斷;

2、hashcode不相等,兩個對象一定不相等;

3、equals方法爲true,則hashcode肯定一樣;

4、equals方法爲false,則hashcode不一定不一樣;

好吧,這不是口訣,這完全是文言文,希望大家在理解的基礎上去記憶,否則光靠背,這東西時間久了就迷糊了。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章