關(guān)于 “ == ”
“ == ”操作符主要比較的是操作符兩端對象的內(nèi)存地址腹侣。如果兩個對象的內(nèi)存地址是一致的,那么就返回true今穿。反之伦籍,則返回false腮出。
話不多說胚嘲,先來看看下面這段代碼:
再來看看會輸出什么:
怎么樣,這個結(jié)果你想到了嗎攻锰?
以下是關(guān)于上面結(jié)果的解釋:
其實在Java中對于字符串的創(chuàng)建娶吞,有兩種形式。
- 一種是形如String a = "Hello"這樣的字面量形式妒蛇;
- 另一種是形如String d = new String("Hello")這樣的構(gòu)造對象的方法绣夺。
字面量形式
在JVM中,為了減少對字符串變量的重復(fù)創(chuàng)建奋蔚,其擁有一段特殊的內(nèi)存空間烈钞,這段內(nèi)存被稱為字符串常量池(constant pool)或者是“字符串字面量池”棵磷。
在程序1-1中,我們首先使用 String a = "Hello" 創(chuàng)建了一個對象沉桌。此時默認字符串常量池中沒有內(nèi)容為“Hello”的對象存在算吩。當JVM在字符串常量池中沒有找到內(nèi)容為"Hello"的對象時偎巢,就會創(chuàng)建一個內(nèi)容為"Hello"的對象,并將它的引用返回給變量a求冷。
那么當我們在后面使用 String b = "Hello" 的時候窍霞,會發(fā)生什么情況呢但金?
首先JVM會在字符串常量池中查詢是否有內(nèi)容為“Hello”的對象。因為此時字符串常量池中已經(jīng)有內(nèi)容為“Hello”的對象了钱磅,故JVM不會創(chuàng)建新的地址空間,而是將原有的“Hello”對象的引用返回給b------所以此時變量a和變量b擁有的是同一個對象引用年柠,即指向的是同一塊內(nèi)存地址彪杉,因此使用 == 操作符對a和b進行比較牵咙,結(jié)果為true洁桌。
使用new來構(gòu)造一個對象
然而當我們new一個字符串對象時,不管字符串常量池中是否有內(nèi)容相同的對象谱轨,JVM都會創(chuàng)造一個新的對象吠谢,所以地址肯定不一樣工坊,因此使用 == 操作符對a和d進行比較,結(jié)果為false罢吃。
關(guān)于equals
在Object類中昭齐,equals方法源碼如下:
我們可以看到阱驾,在Object類中的equals方法其實在內(nèi)部也是使用了“ == ”操作符-----如果兩個對象地址一樣則返回true里覆,反之則返回false。
既然equals方法里面也是使用“ == ”,那為什么還要設(shè)立一個equals方法割去,而不是直接用“ == ”操作符呢?
別急夸赫,這只是Object類里面的equals方法咖城,其實絕大部分Object的子類都對equals方法進行了改寫:
除了會比較內(nèi)存地址切平,以上兩個類中的equals方法還會比較對象所包含的內(nèi)容辐董。如果兩個對象所包含的內(nèi)容相同,equals方法也是返回true苔严。
官方文檔中對equals方法的描述:
equals方法在非空對象引用上實現(xiàn)相等關(guān)系:
- 自反性:對于任何非空引用值x届氢,x.equals(x)都應(yīng)返回true覆旭。
- 對稱性:對于任何非空引用值x和y姐扮,當且僅當y.equals(x)返回true時,x.equals(y)才應(yīng)返回true壤靶。
- 傳遞性:對于任何非空引用值x惊搏、y和z恬惯,如果x.equals(y)返回true,并且y.equals(z)返回true浓恳,那么x.equals(z)應(yīng)返回true颈将。
-
一致性:對于任何非空引用值x和y,多次調(diào)用x.equals(y)始終返回true
或始終返回false颂砸,前提是對象上equals比較中所用的信息沒有被修改死姚。對于任何非空引用值x都毒,x.equals(null)都應(yīng)返回false。
Object類的equals方法實現(xiàn)對象上差別可能性最大的相等關(guān)系保屯;即姑尺,對于任何非空引用值x和y蝠猬,當且僅當x和y引用同一個對象時榆芦,此方法才返回true(x == y具有值true)。
注意:當此方法被重寫時驻右,通常有必要重寫hashCode方法崎淳,以維護hashCode方法的常規(guī)協(xié)定拣凹,該協(xié)定聲明相等對象必須具有相等的哈希碼。
其實在很多時候爬迟,我們都需要改寫equals方法以適應(yīng)我們的實際情況:
下面是一個Person類付呕,包含name和age兩個屬性。
現(xiàn)在我們創(chuàng)建兩個Person對象棒搜,但是name和age分別都設(shè)置一樣的值,同時使用equals方法對這兩個對象進行比較:
雖然這從JVM的角度來看這個程序是對的育韩,可是結(jié)果并不是我們想要的:對象one和對象two雖然是兩個不同的對象筋讨,但是它們包含的元素的值是相同的摸恍,也就是說one和two應(yīng)該都表示的是同一個人-----name 為 EakonZhao立镶,年齡為19∈嚷撸可是為什么調(diào)用equals方法之后輸出的卻是false呢栈顷?
原因:由于我們沒有對equals方法進行改寫嵌巷,所以當我們在調(diào)用equals方法的時候?qū)嶋H上調(diào)用的是Object類的equals方法搪哪。從前面我們可以得知,Object的equals方法在內(nèi)部是直接使用“ == ”操作符對對象進行比較颤难,這樣當然會返回false啦行嗤!
下面我將對equals方法進行改寫垛耳,以滿足我們的需求;
對于改寫equals方法之后是否需要改寫hashCode方法以維護hashCode方法的常規(guī)協(xié)定栈雳,我在介紹完hashCode方法之后會繼續(xù)講
關(guān)于在使用"=="操作符和equals方法時發(fā)生的自動裝箱與自動拆箱現(xiàn)象
簡單介紹一下自動拆箱和自動裝箱
自動裝箱:將Java的基本類型轉(zhuǎn)換成包裝器類型;
自動拆箱:將Java的包裝器類型轉(zhuǎn)換成基本類型霉旗。
如上圖所示厌秒,其實這是一個非常簡單的程序鸵闪,但是實際上Java自動幫我們完成了拆裝箱的操作暑诸。
背景:
在JDK1.5之前个榕,如果我們要生成一個數(shù)值為1的Integer對象,那么我們必須使用下面這行代碼:
可是引入了自動裝箱功能之后夏志,我們只需使用這樣一行代碼就能完成了:
這是如何實現(xiàn)的呢沟蔑?下面我們將上面那段代碼的class文件反編譯一下:
我們可以看到瘦材,其實Java是使用了 Integer的valueOf(int)方法來完成自動裝箱的食棕,而在拆箱過程當中調(diào)用的是Integer的intValue方法错沽。對于其他的包裝器類型來說千埃,其實這兩個過程也是類似的。
裝箱過程是通過調(diào)用包裝器類型的valueOf方法實現(xiàn)的谒臼,拆箱過程是通過調(diào)用包裝器類型的xxValue方法實現(xiàn)的(其中xx代表的是對應(yīng)的基本類型)蜈缤。
讓我們再來看看下面這段代碼:
為什么上面兩條打印代碼會輸出不同的結(jié)果底哥?
原因也很簡單:與字符串常量池類似,這其實也是JVM節(jié)省內(nèi)存的一個方法-----對于Integer類型的對象來說奶陈,如果我們要創(chuàng)建的Integer對象的數(shù)值在 [-128,127]的區(qū)間之內(nèi)附较,那么JVM就會在緩存中查找拒课,看看有沒有已經(jīng)存放在緩存中的數(shù)值一樣的Integer對象事示。如果有肖爵,就返回已經(jīng)存在的對象的引用。
關(guān)于使用equals方法時發(fā)生的自動拆裝箱現(xiàn)象就不贅述了冀自,其實很容易理解熬粗。
小結(jié): “==”運算符其實比較的是地址相不相同余境,而equals方法比較的是值相不相同芳来。
關(guān)于hashCode
官方文檔中隊hashCode方法的描述:
public int hashCode()
返回該對象的哈希碼值。支持此方法是為了提高哈希表(例如java.util.Hashtable
提供的哈希表)的性能佣盒。
hashCode的常規(guī)協(xié)定是:
- 在 Java 應(yīng)用程序執(zhí)行期間沼撕,在對同一對象多次調(diào)用hashCode方法時,必須一致地返回相同的整數(shù)磨总,前提是將對象進行equals比較時所用的信息沒有被修改蚪燕。從某一應(yīng)用程序的一次執(zhí)行到同一應(yīng)用程序的另一次執(zhí)行奔浅,該整數(shù)無需保持一致汹桦。
- 如果根據(jù)equals(Object)方法,兩個對象是相等的钥弯,那么對這兩個對象中的每個對象調(diào)用hashCode方法都必須生成相同的整數(shù)結(jié)果督禽。
- 如果根據(jù)equals(java.lang.Object)方法狈惫,兩個對象不相等,那么對這兩個對象中的任一對象上調(diào)用hashCode方法不要求一定生成不同的整數(shù)結(jié)果忆肾。但是难菌,程序員應(yīng)該意識到蔑滓,為不相等的對象生成不同整數(shù)結(jié)果可以提高哈希表的性能键袱。
實際上蹄咖,由Object類定義的 hashCode 方法確實會針對不同的對象返回不同的整數(shù)。(這一般是通過將該對象的內(nèi)部地址轉(zhuǎn)換成一個整數(shù)來實現(xiàn)的蚜迅,但是 JavaTM
編程語言不需要這種實現(xiàn)技巧谁不。)
上面這段話,簡單的來說就是以下幾點:
- hashCode存在的意義主要是提供查找的快捷性吵血,比如說在Hashtable蹋辅、HashMap中等挫掏。hashCode是用來在散列存儲結(jié)構(gòu)中確定對象存儲的位置的;
- 如果兩個對象相同尉共,即調(diào)用equals方法返回的是true,那么它倆的hashCode值也要相同;
- 如果equals方法被改寫了,那么hashCode方法也盡量要改寫杠河,并且產(chǎn)生hashCode的對象也要和equals的對象保持一致券敌;
- 兩個對象的hashCode相同并不代表兩個對象就一定相同柳洋,也就是不一定適用于equals(java.lang.Object)方法熊镣,只能夠說明這兩個對象在散列存儲對象中绪囱,如Hashtable中,是存放在同一個籃子里的扣甲。(關(guān)于Hashtable的介紹我在后面會開博客探究其源碼)
下面這段話是從別人那里轉(zhuǎn)過來的琉挖,我覺得能幫助理解hashCode:
- hashcode是用來查找的示辈,如果你學(xué)過數(shù)據(jù)結(jié)構(gòu)就應(yīng)該知道,在查找和排序這一章有
例如內(nèi)存中有這樣的位置
01234567
而我有個類坠敷,這個類有個字段叫ID,我要把這個類存放在以上8個位置之一膝迎,如果不用hashcode而任意存放胰耗,那么當查找時就需要到這八個位置里挨個去找柴灯,或者用二分法一類的算法赠群。
但如果用hashcode那就會使效率提高很多。
我們這個類中有個字段叫ID,那么我們就定義我們的hashcode為ID%8突委,然后把我們的類存放在取得得余數(shù)那個位置匀油。比如我們的ID為9勾笆,9除8的余數(shù)為1窝爪,那么我們就把該類存在1這個位置,如果ID是13帅韧,求得的余數(shù)是5忽舟,那么我們就把該類放在5這個位置叮阅。這樣,以后在查找該類時就可以通過ID除8求余數(shù)直接找到存放的位置了挑随。
- 但是如果兩個類有相同的hashcode怎么辦呢兜挨?(我們假設(shè)上面的類的ID不是唯一的),例如9除以8和17除以8的余數(shù)都是1眯分,那么這是不是合法的拌汇,回答是:可以這樣。那么如何判斷呢弊决?在這個時候就需要定義equals了噪舀。
也就是說,我們先通過hashcode來判斷兩個類是否存放某個桶里飘诗,但這個桶里可能有很多類与倡,那么我們就需要再通過equals來在這個桶里找到我們要的類。
那么昆稿。重寫了equals(),為什么還要重寫hashCode()呢溉潭?
想想净响,你要在一個桶里找東西,你必須先要找到這個桶啊岛抄,你不通過重寫hashcode()來找到桶别惦,光重寫equals()有什么用啊
下面是代碼示例:
我新建了一個HashExample類狈茉,里面定義了一個屬性為id夫椭,并改寫了hashCode方法:
現(xiàn)在我new兩個對象,這兩個對象的id我都賦予相同的值氯庆,并將它們兩個存入一個Set(Set中的元素是不重復(fù)的)當中蹭秋,然后分別輸出它們兩個的hashCode以及使用equals方法比較的結(jié)果以及將這個Set也輸出:
以上這個示例,我們只是重寫了hashCode方法堤撵,從上面的結(jié)果可以看出仁讨,雖然兩個對象的hashCode相等,但是實際上兩個對象并不是相等实昨;洞豁,我們沒有重寫equals方法,那么就會調(diào)用object默認的equals方法,是比較兩個對象的引用是不是相同丈挟,顯示這是兩個不同的對象刁卜,兩個對象的引用肯定是不定的。這里我們將生成的對象放到了hashSet中曙咽,而hashSet中只能夠存放唯一的對象沛善,也就是相同的(適用于equals方法)的對象只會存放一個鲁纠,但是這里實際上是兩個對象a,b都被放到了HashSet中,這樣hashSet就失去了他本身的意義了。
現(xiàn)在把equals也改寫一下:
現(xiàn)在我們可以看到醇锚,這兩個對象已經(jīng)完全相等了,并且hashSet中也只存放了一份對象粗俱。