1.前言
最近在上下班的地鐵上把《阿里巴巴Java開發(fā)手冊》讀了一遍,感覺獲益匪淺。讀的過程中也有一些思考和疑惑,就抽時(shí)間親自嘗試了一下。下面用這篇文章把我的問題和答案整理出來。
2.正文
- 手冊里提到,如果重寫equals()方法,就必須重寫hashCode()方法,網(wǎng)上查了一下如何重寫hashCode(),發(fā)現(xiàn)好多重寫方法里都出現(xiàn)了31這個(gè)神奇的數(shù)字。以下是String類的hashCode()實(shí)現(xiàn):
@Override public int hashCode() {
int hash = hashCode;
if (hash == 0) {
if (count == 0) {
return 0;
}
for (int i = 0; i < count; ++i) {
hash = 31 * hash + charAt(i);
}
hashCode = hash;
}
return hash;
}
這個(gè)magic number勾起了我的興趣,重寫hashCode()方法為什么會(huì)不約而同地用到31這個(gè)數(shù)字呢?
在網(wǎng)上查了一下,這個(gè)問題也沒有標(biāo)準(zhǔn)的答案,以下是我查找到的一些比較令人信服的答案:
- 每個(gè)對象根據(jù)值計(jì)算HashCode,這個(gè)code大小雖然不奢求必須唯一(因?yàn)檫@樣通常計(jì)算會(huì)非常慢),但是要盡可能的不要重復(fù),因此基數(shù)要盡量的大且計(jì)算出來的值不要溢出,計(jì)算出來的hash地址越大,所謂的“沖突”就越少,查找起來效率也會(huì)提高。
- 素?cái)?shù)的特性能夠使得它和其他數(shù)相乘后得到的結(jié)果比其他方式更容易產(chǎn)成唯一性,也就是hashcode值的沖突概率最小。
- 31*N可以被編譯器優(yōu)化為左移5位后減N,有較高的性能。
比較權(quán)威的答案是Stack OverFlow上:
According to Joshua Bloch's『Effective Java』:
The value 31 was chosen because it is an odd prime. If it were even and the multiplication overflowed, information would be lost, as multiplication by 2 is equivalent to shifting.
The advantage of using a prime is less clear, but it is traditional.
A nice property of 31 is that the multiplication can be replaced by a shift and a subtraction for better performance: 31 * i == (i << 5) - i.
Modern VMs do this sort of optimization automatically.
翻譯:
根據(jù) Joshua Bloch 的著作『Effective Java』:
設(shè)計(jì)者選擇 31 這個(gè)值是因?yàn)樗且粋€(gè)奇質(zhì)數(shù)。如果它是一個(gè)偶數(shù),在使用乘法當(dāng)中產(chǎn)生數(shù)值溢出時(shí),原有數(shù)字的信息將會(huì)丟失,因?yàn)槌艘远喈?dāng)于位移。
選擇質(zhì)數(shù)的優(yōu)勢不是那么清晰,但是這是一個(gè)傳統(tǒng)。
31 的一個(gè)優(yōu)良的性質(zhì)是:乘法可以被位移和減法替代: 31 * i == (i << 5) - i
現(xiàn)代的 VM 可以自行完成這個(gè)優(yōu)化。
- 手冊里還留下了一個(gè)問題:
List<String> a = new ArrayList<String>();
a.add("1");
a.add("2");
for (String temp : a) {
if ("1".equals(temp)) {
a.remove(temp);
}
}
這段代碼執(zhí)行時(shí)不會(huì)拋出異常,但是如果將equals前面的"1"換成"2"就會(huì)拋出ConcurrentModificationException,使用Iterator來進(jìn)行刪除操作也不會(huì)拋出異常,這是為什么呢?
通過分析源碼發(fā)現(xiàn),在Iterator的remove()和next()方法中都調(diào)用了checkForComodification()方法:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
就是在這里拋出了ConcurrentModificationException.
modCount:修改次數(shù)
expectedModCount:預(yù)期的修改次數(shù)
使用ArrayList的remove(),只會(huì)使modCount++,不會(huì)修改expectedModCount
使用Iterator的remove(),則是expectedModCount = ++modCount
前面提到remove("1")時(shí)不會(huì)拋出異常,這是因?yàn)閒oreach方法其實(shí)也是調(diào)用了Iterator的hasNext()和next(),next()里調(diào)了checkForComodification(),hasNext()卻沒調(diào)用,remove("1")后,hasNext()返回false,就不會(huì)走到next(),所以也就不會(huì)拋出異常了,這其實(shí)只是一個(gè)巧合。
所以:不要在foreach循環(huán)里進(jìn)行元素的remove/add操作,remove元素請使用Iterator方式,如果并發(fā)操作,需要對Iterator對象加鎖。
具體的分析過程可以參考:http://blog.csdn.net/qq_28816195/article/details/78433544
3.結(jié)語
參加工作后才發(fā)現(xiàn),代碼規(guī)范其實(shí)是極其重要的,優(yōu)雅的代碼便于理解和維護(hù),更加節(jié)省時(shí)間。就像通過一個(gè)人的字就能大概看出這是一個(gè)什么樣的人,代碼亦是如此。剛畢業(yè)不久的我,更應(yīng)該培養(yǎng)好的代碼規(guī)范,這對今后的成長必然是大有益處的。