簡要需求:從數據庫查詢一個對象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不一定不一樣;
好吧,這不是口訣,這完全是文言文,希望大家在理解的基礎上去記憶,否則光靠背,這東西時間久了就迷糊了。