Object 通用方法
Object是一個(gè)具體類(lèi)窿侈,但是設(shè)計(jì)它主要是為了擴(kuò)展,其所有的非final方法(equals淤井、hashCode、toString出爹、clone庄吼、finalize)都有明確的通用約定,我們可以在遵守通用約定的前提下override這些方法严就。
注:不遵守通用約定(general contract)復(fù)寫(xiě)Object類(lèi)的非final方法,可以會(huì)導(dǎo)致依賴(lài)于這些約定的類(lèi)(hashMap之類(lèi)的)無(wú)法正常工作器罐。
第8條:覆蓋equals時(shí)請(qǐng)遵守通用約定
Object的equals原意:類(lèi)的實(shí)例只與它本身相同梢为,即比較地址相同,不比較值轰坊。
在以下任意情況下不應(yīng)覆蓋equals方法:
- 類(lèi)的每個(gè)實(shí)例本質(zhì)上都是唯一的铸董,即比較地址不比較值(如Thread)
- 不關(guān)心類(lèi)是否提供了“邏輯相等(值相等)”的測(cè)試功能
- 超類(lèi)已覆蓋了equals方法,且適用于子類(lèi)(如Set實(shí)現(xiàn)類(lèi)從AbstractSet繼承equals方法肴沫,List實(shí)現(xiàn)類(lèi)從AbstractList繼承equals方法)
- 類(lèi)是私有的或是包級(jí)私有的粟害,可以確定其equals不會(huì)被調(diào)用。
Tip:禁止調(diào)用可以在對(duì)應(yīng)的方法體內(nèi)拋出錯(cuò)誤
throw new AssertionError()
如果類(lèi)具有其特有的“邏輯相等”概念颤芬。并且超類(lèi)沒(méi)有實(shí)現(xiàn)期待的行為悲幅,此時(shí)我們需要覆蓋equals方法。這通常屬于“值類(lèi)(value class)”的情況站蝠。值類(lèi)僅僅是一個(gè)表示值的類(lèi)汰具,如Integer或者Date。當(dāng)我們調(diào)用值類(lèi)的equals時(shí)菱魔,往往是為了判斷其值是否相等留荔,而不關(guān)心是否為同一個(gè)對(duì)象(可以使用==判斷是否為同一實(shí)例)。
Tip: Set澜倦、List聚蝶、Map等集合類(lèi)多使用equals判斷key是否相等,所以我們需要在使用這些類(lèi)時(shí)確保我們的equals方法是符合我們的預(yù)期的藻治。
equals方法的通用約定(JavaSE6):
x碘勉、y、z皆為非null引用值
- 自反性(reflextive):
assert x.equals(x);
- 對(duì)稱(chēng)性(symmetric):
if(x.equals(y)) assert y.equals(x);
- 傳遞性(transitive):
if(x.equals(y)&&y.equals(z)) assert x.equals(z);
- 一致性(consistent):如果x栋艳、y未被修改恰聘,則調(diào)用x.equals(y)應(yīng)一直返回true或者一直返回false
- 非空性(Non-nullify):x.equals(null)必須返回false,
assert !x.equals(null)
實(shí)現(xiàn)高質(zhì)量equals的訣竅:
- 使用==操作符檢查“參數(shù)是否為這個(gè)對(duì)象的引用”
- 使用instanceof操作符檢查“參數(shù)是否為正確的類(lèi)型”
- 把參數(shù)轉(zhuǎn)換為正確的類(lèi)型
- 對(duì)于該類(lèi)的“關(guān)鍵域”,逐一比較(見(jiàn)以下Tip)
- 回顧并進(jìn)行單元測(cè)試:對(duì)稱(chēng)性晴叨、傳遞性凿宾、一致性
Tip: 逐一比較的訣竅:
1、對(duì)于不是float兼蕊、double的基本類(lèi)型域初厚,可以直接使用==進(jìn)行比較;
2孙技、對(duì)于對(duì)象引用域产禾,可以遞歸地調(diào)用equals方法進(jìn)行比較;
3牵啦、對(duì)于float域亚情,可以使用Float.compare方法;對(duì)于double域哈雏,可以使用Double.compare方法楞件;
4、對(duì)于數(shù)組域裳瘪,可以使用Array.equals方法
5土浸、使用field == null ? o.field == null : field.equals(o.field)
或(field == o.field) || (field != null) && (field.equals(o.field))
習(xí)慣寫(xiě)法
6、優(yōu)化比較順序:優(yōu)先比較最有可能不一致的域彭羹、比較開(kāi)銷(xiāo)最低的域黄伊。不比較不屬于對(duì)象邏輯狀態(tài)(值)的域,或者冗余域(除非其能代表大量關(guān)鍵域派殷,比較其能節(jié)省大量時(shí)間)
最后的告誡:
- 覆蓋equals時(shí)總要覆蓋hashCode(見(jiàn)第9條)还最;
- 不要企圖讓equals過(guò)于智能;
- 不要將equals聲明中的Object對(duì)象替換為其他的類(lèi)型(這樣是overload而不是override)愈腾,可以添加
@override
注解幫助規(guī)避錯(cuò)誤憋活。
第9條:覆蓋equals時(shí)總要覆蓋hashCode
在Object類(lèi)中,hashCode方法是一個(gè)native方法虱黄,返回值與對(duì)象的存儲(chǔ)地址相關(guān)悦即,計(jì)算方法由jvm決定。
hashCode通用公約(JavaSE6):
- 在應(yīng)用程序的執(zhí)行期間橱乱,只要對(duì)象的equals方法的比較操作所用到的信息沒(méi)有被修改辜梳,那么對(duì)這同一個(gè)對(duì)象調(diào)用多次,hashCode方法都必須始終如一地返回同一個(gè)整數(shù)泳叠。在同一個(gè)程序多次執(zhí)行過(guò)程中作瞄,每次執(zhí)行所返回的整數(shù)可以不一致;
- 如果兩個(gè)對(duì)象根據(jù)equals方法比較是相等的危纫,那么調(diào)用者兩個(gè)對(duì)象的hashCode方法必須返回同一個(gè)整數(shù)宗挥;
- 如果兩個(gè)對(duì)象根據(jù)equals方法比較是不相等的乌庶,那么調(diào)用者兩個(gè)對(duì)象的hashCode方法不一定返回不同整數(shù)。給不相等的對(duì)象產(chǎn)生截然不同的整數(shù)結(jié)果契耿,有可能提高散列表(hash table)的性能瞒大;
注: HashTable、HashMap搪桂、HashSet等散列集合類(lèi)的散列算法實(shí)現(xiàn)往往依賴(lài)于hashCode透敌。假設(shè)我們覆蓋了equals方法,但沒(méi)有覆蓋hashCode踢械,那么在我們理解中equals的對(duì)象酗电,對(duì)于散列集合類(lèi)而言,可能是不相等内列。
高質(zhì)量的hashCode方法的原則:
- 對(duì)于equals的對(duì)象撵术,返回同一個(gè)整數(shù);
- 對(duì)于非equals的對(duì)象话瞧,盡可能返回不一樣的整數(shù)荷荤;
- 為不相等的對(duì)象均勻產(chǎn)生不相等的散列碼。
編寫(xiě)好質(zhì)量的hashCode的訣竅:
- 把某個(gè)非零的常數(shù)值(如17)保存在一個(gè)名為result的int類(lèi)型的變量中移稳;
- 對(duì)于對(duì)象中的每個(gè)關(guān)鍵域f,完成以下步驟:
1会油、為該域計(jì)算int類(lèi)型的散列碼c(見(jiàn)以下Tip)个粱;
2、計(jì)算result = 31 * result +c
翻翩,合并c - 返回result
- 編寫(xiě)單元測(cè)試都许,保證相等的實(shí)例能得到同一個(gè)散列值
TIp: 計(jì)算int類(lèi)型的散列碼c
1、如果該域是boolean類(lèi)型嫂冻,則計(jì)算f ? 1 : 0
胶征;
2、如果該域是byte桨仿、char睛低、short、int類(lèi)型服傍,則計(jì)算(int) f
钱雷;
3、如果該域是long類(lèi)型吹零,則計(jì)算(int)( f ^ (f>>>32))
罩抗;
4、如果該域是float類(lèi)型灿椅,則計(jì)算Float.floatToTintBits(f)
套蒂;
5钞支、如果該域是long類(lèi)型,則計(jì)算'Double.doubleToLongBits(f)'操刀,然后按照步驟3為long計(jì)算散列值烁挟;
6、如果該域是一個(gè)對(duì)象引用馍刮,為null則返回0信夫,否則遞歸調(diào)用hashCode;
7卡啰、如果該域是一個(gè)數(shù)組静稻,調(diào)用Arrays.hashCode
;
在計(jì)算散列碼時(shí)匈辱,只能用到覆蓋的equals函數(shù)比較的關(guān)鍵域振湾,否則相等(equals)的對(duì)象可以會(huì)得到不同的散列值。
初始化常數(shù)值17是任選的亡脸,不為0就可以了押搪,目的在于增加散列值為0的關(guān)鍵域的影響。
31是一個(gè)比較特殊的數(shù)字浅碾,首先它是一個(gè)奇素?cái)?shù)大州,如果乘數(shù)為偶數(shù),則相當(dāng)于移位垂谢,會(huì)增加沖突厦画。再者,其乘法可以被jvm自動(dòng)優(yōu)化:31 * i == (i << 5 ) - i
滥朱。
Tip:如果一個(gè)對(duì)象是不可變的根暑,或者其散列值計(jì)算開(kāi)銷(xiāo)比較大,可以考慮將其散列值在實(shí)例初始化時(shí)計(jì)算后緩存在對(duì)象內(nèi)部徙邻。
第10條:始終要覆蓋toString
在Object類(lèi)中排嫌,toString會(huì)返回類(lèi)名,一個(gè)@符號(hào)缰犁,和散列碼的無(wú)符號(hào)十六進(jìn)制表示法淳地,這通常不是我們所希望看到的。
toString的通用約定指出民鼓,被返回的字符串應(yīng)該是一個(gè)簡(jiǎn)潔的薇芝,信息豐富的,易于閱讀的表達(dá)形式丰嘉,并建議所有的子類(lèi)都o(jì)verride這個(gè)方法夯到。
提供好的toString實(shí)現(xiàn)可以使類(lèi)用起來(lái)更加舒適,特別是在打印對(duì)象信息的時(shí)候饮亏,我們可以之間將對(duì)象作為參數(shù)直接傳入print函數(shù)耍贾,打印出對(duì)象信息阅爽。
在實(shí)際應(yīng)用中,toString方法應(yīng)該返回對(duì)象中包含的所有值得關(guān)注的信息
在實(shí)現(xiàn)toString時(shí)荐开,還應(yīng)考慮是否在文檔中指定返回值的格式付翁。若是指定格式,最好再提供一個(gè)靜態(tài)工廠(chǎng)方法或者構(gòu)造器晃听,以便從這種表示法中轉(zhuǎn)換對(duì)象百侧;若是不指定返回值的格式,則可以保持toString方法的靈活性能扒,以便在后期改進(jìn)格式佣渴。
第11條:謹(jǐn)慎覆蓋clone方法
Cloneable接口的目的在于表明這個(gè)對(duì)象是允許被clone的,然而它并沒(méi)有成功地達(dá)到這個(gè)目的初斑。其主要原因在于它并沒(méi)有包含任何方法辛润,而Object的clone方法是受保護(hù)的,需要反射調(diào)用见秤,而且反射調(diào)用也不一定會(huì)成功砂竖。
Cloneable接口的作用在于表明:實(shí)現(xiàn)了Cloneable接口的類(lèi),應(yīng)該覆蓋了Object的clone方法鹃答。
clone方法的通用公約:
創(chuàng)建和返回該對(duì)象的一個(gè)拷貝乎澄。這個(gè)拷貝的精確含義由該對(duì)象的類(lèi)決定。一般有以下含義:
- 對(duì)于任何對(duì)象x测摔,表達(dá)式
x.clone() != x
將會(huì)是true - 對(duì)于任何對(duì)象x三圆,表達(dá)式
x.clone().getClass() == x.getClass
將會(huì)是true - 對(duì)于任何對(duì)象x,表達(dá)式
x.clone().equals(x)
將會(huì)是true
拷貝對(duì)象往往會(huì)導(dǎo)致創(chuàng)建它的類(lèi)的一個(gè)新實(shí)例避咆,但它同時(shí)要求拷貝類(lèi)內(nèi)部的數(shù)據(jù)結(jié)構(gòu)。這個(gè)過(guò)程沒(méi)有調(diào)用構(gòu)造器修噪。
覆蓋clone方法查库,必須保證其拷貝是深度拷貝,這往往需要其超類(lèi)實(shí)現(xiàn)了良好的clone方法黄琼。其次樊销,對(duì)于拷貝對(duì)象內(nèi)部的引用對(duì)象,我們需要實(shí)例化一個(gè)新的對(duì)象脏款,并修改其值围苫,而不能簡(jiǎn)單復(fù)制引用。覆蓋clone方法是一件十分吃力不討好的事撤师。
所有實(shí)現(xiàn)了Cloneable接口的類(lèi)都應(yīng)該用一個(gè)公有的方法覆蓋clone剂府。此公有方法首先調(diào)用super.clone()
,然后修正任何需要修正的域。
除非你擴(kuò)展了一個(gè)實(shí)現(xiàn)Cloneable接口的類(lèi)剃盾,否則最好提供某些其他的途徑代替clone方法腺占,或者干脆不提供這樣的功能淤袜。
我們可以提供一個(gè)拷貝構(gòu)造器或者拷貝工廠(chǎng)來(lái)替代clone方法:
// 拷貝構(gòu)造器:以拷貝對(duì)象為參數(shù)
public Yum(Yum yum)
// 拷貝工廠(chǎng)
public static Yum newInstance(Yum yum)
更進(jìn)一步,我們可以提供一個(gè)轉(zhuǎn)換構(gòu)造器衰伯,其參數(shù)是一個(gè)超類(lèi)/接口的對(duì)象铡羡。按照慣例,所有通用集合實(shí)現(xiàn)都提供了一個(gè)轉(zhuǎn)換構(gòu)造器意鲸,如一個(gè)HashSet s
,可以通過(guò)TreeSet(s)
轉(zhuǎn)換類(lèi)型烦周。
注意:對(duì)于一個(gè)專(zhuān)門(mén)為了繼承而設(shè)計(jì)的類(lèi),如果你未能提供香味良好的受保護(hù)的clone方法怎顾,它的子類(lèi)就不可能實(shí)現(xiàn)Cloneable方法读慎。
第12條:考慮實(shí)現(xiàn)Comparable接口
compareTo方法是Comparable接口唯一的方法,它與Object類(lèi)的通用方法有很大的相似性杆勇。compareTo方法不但允許進(jìn)行簡(jiǎn)單的等同性比較贪壳,而且允許執(zhí)行順序比較。類(lèi)實(shí)現(xiàn)了Comparable接口蚜退,就表明它的實(shí)例具有內(nèi)在的排序關(guān)系闰靴。對(duì)于實(shí)現(xiàn)了Comparable接口的對(duì)象數(shù)組進(jìn)行排序:Arrays.sort(a)
一旦類(lèi)實(shí)現(xiàn)了Comparable接口,它就可以跟許多泛型算法以及依賴(lài)于該接口的集合實(shí)現(xiàn)進(jìn)項(xiàng)協(xié)作钻注。事實(shí)上蚂且,java的所有公共值類(lèi)都實(shí)現(xiàn)了這個(gè)接口。假設(shè)你正在編寫(xiě)一個(gè)值類(lèi)幅恋,它具有非常明顯的內(nèi)在排序關(guān)系杏死,那么你就應(yīng)該堅(jiān)決考慮實(shí)現(xiàn)這個(gè)接口。
compareTo的通用公約:
將這個(gè)對(duì)象與指定的對(duì)象進(jìn)行比較捆交,當(dāng)對(duì)象
- 小于指定對(duì)象時(shí)淑翼,返回負(fù)整數(shù);
- 等于指定對(duì)象時(shí)品追,返回0玄括;
- 大于指定對(duì)象時(shí),返回正整數(shù)肉瓦;
- 無(wú)法與指定對(duì)象進(jìn)行比較遭京,拋出
ClassCastException
與equals類(lèi)似,compareTo同樣有自反型泞莉、傳遞性哪雕、對(duì)稱(chēng)性
編寫(xiě)compareTo方法與編寫(xiě)equals方法非常相似,但也有一些區(qū)別:
- 不用類(lèi)型檢查與轉(zhuǎn)換鲫趁,compareTo方法的參數(shù)是靜態(tài)類(lèi)型斯嚎,不是Object
- 當(dāng)參數(shù)為null時(shí),應(yīng)該拋出NullPointException