Java (堆和棧)灾螃,內(nèi)存地址题翻,==,equals腰鬼,hashCode

轉(zhuǎn)https://blog.csdn.net/LL1187740947/article/details/78419637

問(wèn)題的引入:

問(wèn)題一:
String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true

問(wèn)題二:
String str1 =new String ("abc");
String str2 =new String ("abc");
System.out.println(str1==str2); // false

問(wèn)題三:
String s1 = "ja";
String s2 = "va";
String s3 = "java";
String s4 = s1 + s2;
System.out.println(s3 == s4);//false
System.out.println(s3.equals(s4));//true

由于以上問(wèn)題讓我含糊不清嵌赠,于是特地搜集了一些有關(guān)java內(nèi)存分配的資料,以下是網(wǎng)摘:

Java 中的堆和棧
Java把內(nèi)存劃分成兩種:一種是棧內(nèi)存,一種是堆內(nèi)存熄赡。

在函數(shù)中定義的一些基本類型的變量和對(duì)象的引用變量都在函數(shù)的棧內(nèi)存中分配姜挺。

當(dāng)在一段代碼塊定義一個(gè)變量時(shí),Java就在棧中為這個(gè)變量分配內(nèi)存空間彼硫,當(dāng)超過(guò)變量的作用域后炊豪,Java會(huì)自動(dòng)釋放掉為該變量所分配的內(nèi)存空間凌箕,該內(nèi)存空間可以立即被另作他用。

堆內(nèi)存用來(lái)存放由new創(chuàng)建的對(duì)象和數(shù)組词渤。

在堆中分配的內(nèi)存牵舱,由Java虛擬機(jī)的自動(dòng)垃圾回收器來(lái)管理。

在堆中產(chǎn)生了一個(gè)數(shù)組或?qū)ο蠛笕迸埃€可以在棧中定義一個(gè)特殊的變量芜壁,讓棧中這個(gè)變量的取值等于數(shù)組或?qū)ο笤诙褍?nèi)存中的首地址,棧中的這個(gè)變量就成了數(shù)組或?qū)ο蟮囊米兞俊?/p>

引用變量就相當(dāng)于是為數(shù)組或?qū)ο笃鸬囊粋€(gè)名稱高氮,以后就可以在程序中使用棧中的引用變量來(lái)訪問(wèn)堆中的數(shù)組或?qū)ο蟆?/p>

具體的說(shuō):
棧與堆都是Java用來(lái)在Ram中存放數(shù)據(jù)的地方慧妄。與C++不同,Java自動(dòng)管理?xiàng):投鸭羯郑绦騿T不能直接地設(shè)置椚停或堆。

Java的堆是一個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū),類的(對(duì)象從中分配空間紊浩。這些對(duì)象通過(guò)new窖铡、newarray、anewarray和multianewarray等指令建立坊谁,它們不需要程序代碼來(lái)顯式的釋放费彼。堆是由垃圾回收來(lái)負(fù)責(zé)的,堆的優(yōu)勢(shì)是可以動(dòng)態(tài)地分配內(nèi)存大小口芍,生存期也不必事先告訴編譯器箍铲,因?yàn)樗窃谶\(yùn)行時(shí)動(dòng)態(tài)分配內(nèi)存的,Java的垃圾收集器會(huì)自動(dòng)收走這些不再使用的數(shù)據(jù)鬓椭。但缺點(diǎn)是颠猴,由于要在運(yùn)行時(shí)動(dòng)態(tài)分配內(nèi)存,存取速度較慢小染。

棧的優(yōu)勢(shì)是翘瓮,存取速度比堆要快,僅次于寄存器裤翩,棧數(shù)據(jù)可以共享资盅。但缺點(diǎn)是,存在棧中的數(shù)據(jù)大小與生存期必須是確定的踊赠,缺乏靈活性呵扛。棧中主要存放一些基本類型的變量(,int, short, long, byte, float, double, boolean, char)和對(duì)象句柄。

棧有一個(gè)很重要的特殊性筐带,就是存在棧中的數(shù)據(jù)可以共享今穿。假設(shè)我們同時(shí)定義:
int a = 3;
int b = 3;
編譯器先處理int a = 3伦籍;首先它會(huì)在棧中創(chuàng)建一個(gè)變量為a的引用蓝晒,然后查找棧中是否有3這個(gè)值腮出,如果沒(méi)找到,就將3存放進(jìn)來(lái)拔创,然后將a指向3利诺。接著處理int b = 3;在創(chuàng)建完b的引用變量后剩燥,因?yàn)樵跅V幸呀?jīng)有3這個(gè)值慢逾,便將b直接指向3。這樣灭红,就出現(xiàn)了a與b同時(shí)均指向3的情況侣滩。這時(shí),如果再令a=4变擒;那么編譯器會(huì)重新搜索棧中是否有4值君珠,如果沒(méi)有,則將4存放進(jìn)來(lái)娇斑,并令a指向4策添;如果已經(jīng)有了毫缆,則直接將a指向這個(gè)地址唯竹。因此a值的改變不會(huì)影響到b的值。要注意這種數(shù)據(jù)的共享與兩個(gè)對(duì)象的引用同時(shí)指向一個(gè)對(duì)象的這種共享是不同的,因?yàn)檫@種情況a的修改并不會(huì)影響到b, 它是由編譯器完成的旺拉,它有利于節(jié)省空間晋涣。而一個(gè)對(duì)象引用變量修改了這個(gè)對(duì)象的內(nèi)部狀態(tài),會(huì)影響到另一個(gè)對(duì)象引用變量赌莺。

String是一個(gè)特殊的包裝類數(shù)據(jù)【〕可以用:
String str = new String("abc");
String str = "abc";
兩種的形式來(lái)創(chuàng)建续搀,第一種是用new()來(lái)新建對(duì)象的禁舷,它會(huì)在存放于堆中牵咙。每調(diào)用一次就會(huì)創(chuàng)建一個(gè)新的對(duì)象碟嘴。
而第二種是先在棧中創(chuàng)建一個(gè)對(duì)String類的對(duì)象引用變量str雀瓢,然后查找棧中有沒(méi)有存放"abc"玉掸,如果沒(méi)有司浪,則將"abc"存放進(jìn)棧,并令str指向”abc”,如果已經(jīng)有”abc” 則直接令str指向“abc”憔足。

比較類里面的數(shù)值是否相等時(shí)揭绑,用equals()方法他匪;當(dāng)測(cè)試兩個(gè)包裝類的引用是否指向同一個(gè)對(duì)象時(shí)菇存,用==,下面用例子說(shuō)明上面的理論邦蜜。
String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true
可以看出str1和str2是指向同一個(gè)對(duì)象的依鸥。

String str1 =new String ("abc");
String str2 =new String ("abc");
System.out.println(str1==str2); // false
用new的方式是生成不同的對(duì)象。每一次生成一個(gè)悼沈。

因此用第一種方式創(chuàng)建多個(gè)”abc”字符串,在內(nèi)存中其實(shí)只存在一個(gè)對(duì)象而已. 這種寫(xiě)法有利與節(jié)省內(nèi)存空間. 同時(shí)它可以在一定程度上提高程序的運(yùn)行速度贱迟,因?yàn)镴VM會(huì)自動(dòng)根據(jù)棧中數(shù)據(jù)的實(shí)際情況來(lái)決定是否有必要?jiǎng)?chuàng)建新對(duì)象。而對(duì)于String str = new String("abc")絮供;的代碼衣吠,則一概在堆中創(chuàng)建新對(duì)象,而不管其字符串值是否相等壤靶,是否有必要?jiǎng)?chuàng)建新對(duì)象缚俏,從而加重了程序的負(fù)擔(dān)。

另一方面, 要注意: 我們?cè)谑褂弥T如String str = "abc";的格式定義類時(shí)袍榆,總是想當(dāng)然地認(rèn)為,創(chuàng)建了String類的對(duì)象str塘揣。擔(dān)心陷阱包雀!對(duì)象可能并沒(méi)有被創(chuàng)建!而可能只是指向一個(gè)先前已經(jīng)創(chuàng)建的對(duì)象亲铡。只有通過(guò)new()方法才能保證每次都創(chuàng)建一個(gè)新的對(duì)象才写。由于String類的immutable性質(zhì),當(dāng)String變量需要經(jīng)常變換其值時(shí)奖蔓,應(yīng)該考慮使用StringBuffer類赞草,以提高程序效率。

java中內(nèi)存分配策略及堆和棧的比較
1吆鹤、內(nèi)存分配策略
按照編譯原理的觀點(diǎn),程序運(yùn)行時(shí)的內(nèi)存分配有三種策略,分別是靜態(tài)的,棧式的,和堆式的.
靜態(tài)存儲(chǔ)分配是指在編譯時(shí)就能確定每個(gè)數(shù)據(jù)目標(biāo)在運(yùn)行時(shí)刻的存儲(chǔ)空間需求,因而在編譯時(shí)就可以給他們分配固定的內(nèi)存空間.這種分配策略要求程序代碼中不允許有可變數(shù)據(jù)結(jié)構(gòu)(比如可變數(shù)組)的存在,也不允許有嵌套或者遞歸的結(jié)構(gòu)出現(xiàn),因?yàn)樗鼈兌紩?huì)導(dǎo)致編譯程序無(wú)法計(jì)算準(zhǔn)確的存儲(chǔ)空間需求.
棧式存儲(chǔ)分配也可稱為動(dòng)態(tài)存儲(chǔ)分配,是由一個(gè)類似于堆棧的運(yùn)行棧來(lái)實(shí)現(xiàn)的.和靜態(tài)存儲(chǔ)分配相反,在棧式存儲(chǔ)方案中,程序?qū)?shù)據(jù)區(qū)的需求在編譯時(shí)是完全未知的,只有到運(yùn)行的時(shí)候才能夠知道,但是規(guī)定在運(yùn)行中進(jìn)入一個(gè)程序模塊時(shí),必須知道該程序模塊所需的數(shù)據(jù)區(qū)大小才能夠?yàn)槠浞峙鋬?nèi)存.和我們?cè)跀?shù)據(jù)結(jié)構(gòu)所熟知的棧一樣, 棧式存儲(chǔ)分配按照先進(jìn)后出的原則進(jìn)行分配厨疙。
靜態(tài)存儲(chǔ)分配要求在編譯時(shí)能知道所有變量的存儲(chǔ)要求,棧式存儲(chǔ)分配要求在過(guò)程的入口處必須知道所有的存儲(chǔ)要求,而堆式存儲(chǔ)分配則專門(mén)負(fù)責(zé)在編譯時(shí)或運(yùn)行時(shí)模塊入口處都無(wú)法確定存儲(chǔ)要求的數(shù)據(jù)結(jié)構(gòu)的內(nèi)存分配,比如可變長(zhǎng)度串和對(duì)象實(shí)例.堆由大片的可利用塊或空閑塊組成,堆中的內(nèi)存可以按照任意順序分配和釋放.

2、堆和棧的比較
上面的定義從編譯原理的教材中總結(jié)而來(lái),除靜態(tài)存儲(chǔ)分配之外,都顯得很呆板和難以理解,下面撇開(kāi)靜態(tài)存儲(chǔ)分配,集中比較堆和棧:
從堆和棧的功能和作用來(lái)通俗的比較,堆主要用來(lái)存放對(duì)象的疑务,棧主要是用來(lái)執(zhí)行程序的.而這種不同又主要是由于堆和棧的特點(diǎn)決定的:
在編程中沾凄,例如C/C++中,所有的方法調(diào)用都是通過(guò)棧來(lái)進(jìn)行的,所有的局部變量,形式參數(shù)都是從棧中分配內(nèi)存空間的知允。實(shí)際上也不是什么分配,只是從棧頂向上用就行,就好像工廠中的傳送帶(conveyor belt)一樣,Stack Pointer會(huì)自動(dòng)指引你到放東西的位置,你所要做的只是把東西放下來(lái)就行.退出函數(shù)的時(shí)候撒蟀,修改棧指針就可以把棧中的內(nèi)容銷(xiāo)毀.這樣的模式速度最快, 當(dāng)然要用來(lái)運(yùn)行程序了.需要注意的是,在分配的時(shí)候,比如為一個(gè)即將要調(diào)用的程序模塊分配數(shù)據(jù)區(qū)時(shí),應(yīng)事先知道這個(gè)數(shù)據(jù)區(qū)的大小,也就說(shuō)是雖然分配是在程序運(yùn)行時(shí)進(jìn)行的,但是分配的大小多少是確定的,不變的,而這個(gè)"大小多少"是在編譯時(shí)確定的,不是在運(yùn)行時(shí).
堆是應(yīng)用程序在運(yùn)行的時(shí)候請(qǐng)求操作系統(tǒng)分配給自己內(nèi)存,由于從操作系統(tǒng)管理的內(nèi)存分配,所以在分配和銷(xiāo)毀時(shí)都要占用時(shí)間温鸽,因此用堆的效率非常低.但是堆的優(yōu)點(diǎn)在于,編譯器不必知道要從堆里分配多少存儲(chǔ)空間保屯,也不必知道存儲(chǔ)的數(shù)據(jù)要在堆里停留多長(zhǎng)的時(shí)間,因此,用堆保存數(shù)據(jù)時(shí)會(huì)得到更大的靈活性。事實(shí)上,面向?qū)ο蟮亩鄳B(tài)性,堆內(nèi)存分配是必不可少的,因?yàn)槎鄳B(tài)變量所需的存儲(chǔ)空間只有在運(yùn)行時(shí)創(chuàng)建了對(duì)象之后才能確定.在C++中涤垫,要求創(chuàng)建一個(gè)對(duì)象時(shí)姑尺,只需用 new命令編制相關(guān)的代碼即可。執(zhí)行這些代碼時(shí)雹姊,會(huì)在堆里自動(dòng)進(jìn)行數(shù)據(jù)的保存.當(dāng)然股缸,為達(dá)到這種靈活性,必然會(huì)付出一定的代價(jià):在堆里分配存儲(chǔ)空間時(shí)會(huì)花掉更長(zhǎng)的時(shí)間吱雏!這也正是導(dǎo)致我們剛才所說(shuō)的效率低的原因,看來(lái)列寧同志說(shuō)的好,人的優(yōu)點(diǎn)往往也是人的缺點(diǎn),人的缺點(diǎn)往往也是人的優(yōu)點(diǎn)(暈~).

3敦姻、JVM中的堆和棧
JVM是基于堆棧的虛擬機(jī).JVM為每個(gè)新創(chuàng)建的線程都分配一個(gè)堆棧.也就是說(shuō),對(duì)于一個(gè)Java程序來(lái)說(shuō),它的運(yùn)行就是通過(guò)對(duì)堆棧的操作來(lái)完成的歧杏。堆棧以幀為單位保存線程的狀態(tài)镰惦。JVM對(duì)堆棧只進(jìn)行兩種操作:以幀為單位的壓棧和出棧操作。
我們知道,某個(gè)線程正在執(zhí)行的方法稱為此線程的當(dāng)前方法.我們可能不知道,當(dāng)前方法使用的幀稱為當(dāng)前幀犬绒。當(dāng)線程激活一個(gè)Java方法,JVM就會(huì)在線程的 Java堆棧里新壓入一個(gè)幀旺入。這個(gè)幀自然成為了當(dāng)前幀.在此方法執(zhí)行期間,這個(gè)幀將用來(lái)保存參數(shù),局部變量,中間計(jì)算過(guò)程和其他數(shù)據(jù).這個(gè)幀在這里和編譯原理中的活動(dòng)紀(jì)錄的概念是差不多的.
從Java的這種分配機(jī)制來(lái)看,堆棧又可以這樣理解:堆棧(Stack)是操作系統(tǒng)在建立某個(gè)進(jìn)程時(shí)或者線程(在支持多線程的操作系統(tǒng)中是線程)為這個(gè)線程建立的存儲(chǔ)區(qū)域,該區(qū)域具有先進(jìn)后出的特性。
每一個(gè)Java應(yīng)用都唯一對(duì)應(yīng)一個(gè)JVM實(shí)例茵瘾,每一個(gè)實(shí)例唯一對(duì)應(yīng)一個(gè)堆礼华。應(yīng)用程序在運(yùn)行中所創(chuàng)建的所有類實(shí)例或數(shù)組都放在這個(gè)堆中,并由應(yīng)用所有的線程共享.跟C/C++不同,Java中分配堆內(nèi)存是自動(dòng)初始化的拗秘。Java中所有對(duì)象的存儲(chǔ)空間都是在堆中分配的圣絮,但是這個(gè)對(duì)象的引用卻是在堆棧中分配,也就是說(shuō)在建立一個(gè)對(duì)象時(shí)從兩個(gè)地方都分配內(nèi)存,在堆中分配的內(nèi)存實(shí)際建立這個(gè)對(duì)象雕旨,而在堆棧中分配的內(nèi)存只是一個(gè)指向這個(gè)堆對(duì)象的指針(引用)而已扮匠。

JVM運(yùn)行時(shí),將內(nèi)存分為堆和棧凡涩,堆中存放的是創(chuàng)建的對(duì)象棒搜,JAVA字符串對(duì)象內(nèi)存實(shí)現(xiàn)時(shí),在堆中開(kāi)辟了一快很小的內(nèi)存活箕,叫字符串常量池力麸,用來(lái)存放特定的字符串對(duì)象。
關(guān)于String對(duì)象的創(chuàng)建育韩,兩種方式是不同的末盔,第一種不用new的簡(jiǎn)單語(yǔ)法,即
String s1="JAVA";
創(chuàng)建步驟是先看常量池中有沒(méi)有與"JAVA"相同的的字符串對(duì)象座慰,如果有陨舱,將s1指向該對(duì)象,若沒(méi)有版仔,則創(chuàng)建一個(gè)新對(duì)象游盲,并讓s1指向它。
第二種是new語(yǔ)法
String s2=new String ("JAVA");
這種語(yǔ)法是在堆而不是在常量池中創(chuàng)建對(duì)象蛮粮,并將s2指向它益缎,然后去字符串常量池中看看,是否有與之相同的內(nèi)容的對(duì)象然想,如果有莺奔,則將new出來(lái)的字符串對(duì)象與字符串常量池中的對(duì)象聯(lián)系起來(lái),如果沒(méi)有变泄,則在字符串常量池中再創(chuàng)建一個(gè)包含該內(nèi)容的字符串對(duì)象令哟,并將堆內(nèi)存中的對(duì)象與字符串常量池中新建出來(lái)的對(duì)象聯(lián)系起來(lái)。
這就是字符串的一次投入妨蛹,終生回報(bào)的內(nèi)存機(jī)制屏富,對(duì)字符串的比較帶來(lái)好處。
http://blog.csdn.net/xyz1982510/archive/2008/09/12/2916467.aspx

  1. 棧(stack)與堆(heap)都是Java用來(lái)在Ram中存放數(shù)據(jù)的地方蛙卤。與C++不同狠半,Java自動(dòng)管理?xiàng):投沿溃绦騿T不能直接地設(shè)置棧或堆神年。

  2. 棧的優(yōu)勢(shì)是已维,存取速度比堆要快,僅次于直接位于CPU中的寄存器已日。但缺點(diǎn)是衣摩,存在棧中的數(shù)據(jù)大小與生存期必須是確定的,缺乏靈活性捂敌。另外,棧數(shù)據(jù)可以共享既琴,詳見(jiàn)第3點(diǎn)占婉。堆的優(yōu)勢(shì)是可以動(dòng)態(tài)地分配內(nèi)存大小,生存期也不必事先告訴編譯器甫恩,Java的垃圾收集器會(huì)自動(dòng)收走這些不再使用的數(shù)據(jù)逆济。但缺點(diǎn)是,由于要在運(yùn)行時(shí)動(dòng)態(tài)分配內(nèi)存磺箕,存取速度較慢奖慌。

  3. Java中的數(shù)據(jù)類型有兩種。
    一種是基本類型(primitive types)出于追求速度的原因松靡,就存在于棧中简僧。
    出于追求速度的原因,就存在于棧中雕欺。
    另一種是包裝類數(shù)據(jù)岛马,如Integer, String, Double等將相應(yīng)的基本數(shù)據(jù)類型包裝起來(lái)的類。這些類數(shù)據(jù)全部存在于堆中屠列,Java用new()語(yǔ)句來(lái)顯示地告訴編譯器啦逆,在運(yùn)行時(shí)才根據(jù)需要?jiǎng)討B(tài)創(chuàng)建,因此比較靈活笛洛,但缺點(diǎn)是要占用更多的時(shí)間夏志。

  4. String是一個(gè)特殊的包裝類數(shù)據(jù)。即可以用String str = new String("abc");的形式來(lái)創(chuàng)建苛让,也可以用String str = "abc"沟蔑;的形式來(lái)創(chuàng)建。前者是規(guī)范的類的創(chuàng)建過(guò)程狱杰,即在Java中溉贿,一切都是對(duì)象,而對(duì)象是類的實(shí)例浦旱,全部通過(guò)new()的形式來(lái)創(chuàng)建宇色。

值得注意的是,一般String類中字符串值都是直接存值的。但像String str = "abc"宣蠕;這種場(chǎng)合下例隆,其字符串值卻是保存了一個(gè)指向存在棧中數(shù)據(jù)的引用!

用new()來(lái)新建對(duì)象的抢蚀,都會(huì)在堆中創(chuàng)建镀层,而且其字符串是單獨(dú)存值的,即使與棧中的數(shù)據(jù)相同皿曲,也不會(huì)與棧中的數(shù)據(jù)共享唱逢。

使用String str = "abc";的方式屋休,可以在一定程度上提高程序的運(yùn)行速度坞古,因?yàn)镴VM會(huì)自動(dòng)根據(jù)棧中數(shù)據(jù)的實(shí)際情況來(lái)決定是否有必要?jiǎng)?chuàng)建新對(duì)象。而對(duì)于String str = new String("abc")劫樟;的代碼痪枫,則一概在堆中創(chuàng)建新對(duì)象,而不管其字符串值是否相等叠艳,是否有必要?jiǎng)?chuàng)建新對(duì)象奶陈,從而加重了程序的負(fù)擔(dān)。

字符串池在DataSegument里附较。既不在堆也不再棧中吃粒。 用+創(chuàng)建應(yīng)該是在堆里。
應(yīng)該說(shuō)是 特殊的堆
因?yàn)橄鄬?duì)于其他堆中的對(duì)象一旦失去引用就可能會(huì)被當(dāng)做垃圾回收掉
但是字符串對(duì)象就算失去唯一的引用也不會(huì)被回收

127 以下的整數(shù)是相等的 128以上就作為不同對(duì)象處理了

Integer i=100;
Integer j=100;
System.out.println(i==j); // 打印 true

Integer i=200;
Integer j=200;
System.out.println(i==j); //打印false

Java中的==和equals淺見(jiàn)
java中的數(shù)據(jù)類型拒课,可分為兩類:
1.基本數(shù)據(jù)類型声搁,也稱原始數(shù)據(jù)類型。byte,short,char,int,long,float,double,boolean
他們之間的比較捕发,應(yīng)用雙等號(hào)(==),比較的是他們的值疏旨。
2.復(fù)合數(shù)據(jù)類型(類)
當(dāng)他們用(==)進(jìn)行比較的時(shí)候,比較的是他們?cè)趦?nèi)存中的存放地址扎酷,所以檐涝,除非是同一個(gè)new出來(lái)的對(duì)象,他們的比較后的結(jié)果為true法挨,否則比較后結(jié)果為false谁榜。
對(duì)于復(fù)合數(shù)據(jù)類型之間進(jìn)行equals比較,在沒(méi)有覆寫(xiě)equals方法的情況下凡纳,他們之間的比較還是基于他們?cè)趦?nèi)存中的存放位置的地址值的窃植,因?yàn)镺bject的equals方法也是用雙等號(hào)(==)進(jìn)行比較的,所以比較后的結(jié)果跟雙等號(hào)(==)的結(jié)果相同荐糜。
//Object中的equals方法
public boolean equals(Object obj) {
return (this == obj);
}

當(dāng)然巷怜,你也可以重寫(xiě)Object的equals方法葛超,這兒就有個(gè)問(wèn)題啦,參加公司筆試的時(shí)候相信N多人都被要求回答過(guò)這樣的問(wèn)題:在重寫(xiě)了對(duì)象的equals方法后延塑,還需要重寫(xiě)hashCode方法嗎绣张?為什么?
我認(rèn)為关带,出于程序完整性的考慮侥涵,在重寫(xiě)了對(duì)象的equals方法后,是有必要重寫(xiě)對(duì)象的hashCode方法的宋雏。
因?yàn)槲咂阒貙?xiě)了equals方法,你調(diào)用它來(lái)進(jìn)行對(duì)象間的比較磨总,你可以達(dá)到你的比較目的嗦明,但是,當(dāng)你想將你的對(duì)象存入類似HashSet這類對(duì)象中時(shí)舍败,問(wèn)題就出現(xiàn)了(沒(méi)有重寫(xiě)hashCode方法的情況下)。
Set(集)中是不允許有重復(fù)的值的敬拓,而判斷值是否重復(fù)邻薯,是通過(guò)比較他們的hashCode值的。你通過(guò)你重寫(xiě)后的equals比較對(duì)象乘凸,結(jié)果是相等厕诡,但用hashCode值比較他們時(shí)是不相等的,所以营勤,為了比較結(jié)果的一致性灵嫌,需要重寫(xiě)hashCode方法。
下面是String類重寫(xiě)了的equals方法和hashCode方法:

//此方法的目的是葛作,實(shí)現(xiàn)在不同的String對(duì)象之間比較寿羞,比較的是他們的字符串值
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value; //字符串的值,用字符數(shù)組表示
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}

//返回的值是基于字符串的值運(yùn)算出來(lái)的赂蠢,
//字符串值相等則他們的hashCode值也相等,否則绪穆,不相等

public int hashCode() {
int h = hash;
if (h == 0) {
int off = offset;
char val[] = value; //字符串的值,用字符數(shù)組表示
int len = count;
//基于字符串的值產(chǎn)生hash值
for (int i = 0; i < len; i++) {
h = 31*h + val[off++];
}
hash = h;
}
return h;
}

問(wèn):對(duì)象的hashcode是用來(lái)干什么的虱岂?
簡(jiǎn)答:容器類經(jīng)常用到hascode,比如說(shuō)set判斷重復(fù)值玖院,比如說(shuō)hashmap散列。等等第岖。

解析Java對(duì)象的equals()和hashCode()的使用
文章分類:Java編程
前言
在Java語(yǔ)言中难菌,equals()和hashCode()兩個(gè)函數(shù)的使用是緊密配合的,你要是自己設(shè)計(jì)其中一個(gè)蔑滓,就要設(shè)計(jì)另外一個(gè)郊酒。在多數(shù)情況下遇绞,這兩個(gè)函數(shù)是不用考慮的,直接使用它們的默認(rèn)設(shè)計(jì)就可以了猎塞。但是在一些情況下试读,這兩個(gè)函數(shù)最好是自己設(shè)計(jì),才能確保整個(gè)程序的正常運(yùn)行荠耽。最常見(jiàn)的是當(dāng)一個(gè)對(duì)象被加入收集對(duì)象(collection object)時(shí)钩骇,這兩個(gè)函數(shù)必須自己設(shè)計(jì)。更細(xì)化的定義是:如果你想將一個(gè)對(duì)象A放入另一個(gè)收集對(duì)象B里铝量,或者使用這個(gè)對(duì)象A為查找一個(gè)元對(duì)象在收集對(duì)象B里位置的鑰匙倘屹,并支持是否容納,刪除收集對(duì)象B里的元對(duì)象這樣的操作慢叨,那么纽匙,equals()和hashCode()函數(shù)必須開(kāi)發(fā)者自己定義。其他情況下拍谐,這兩個(gè)函數(shù)是不需要定義的烛缔。
equals():
它是用于進(jìn)行兩個(gè)對(duì)象的比較的,是對(duì)象內(nèi)容的比較轩拨,當(dāng)然也能用于進(jìn)行對(duì)象參閱值的比較践瓷。什么是對(duì)象參閱值的比較?就是兩個(gè)參閱變量的值得比較亡蓉,我們都知道參閱變量的值其實(shí)就是一個(gè)數(shù)字晕翠,這個(gè)數(shù)字可以看成是鑒別不同對(duì)象的代號(hào)。兩個(gè)對(duì)象參閱值的比較砍濒,就是兩個(gè)數(shù)字的比較淋肾,兩個(gè)代號(hào)的比較。這種比較是默認(rèn)的對(duì)象比較方式爸邢,在Object這個(gè)對(duì)象中樊卓,這種方式就已經(jīng)設(shè)計(jì)好了。所以你也不用自己來(lái)重寫(xiě)杠河,浪費(fèi)不必要的時(shí)間简识。
對(duì)象內(nèi)容的比較才是設(shè)計(jì)equals()的真正目的,Java語(yǔ)言對(duì)equals()的要求如下感猛,這些要求是必須遵循的七扰。否則,你就不該浪費(fèi)時(shí)間:
* 對(duì)稱性:如果x.equals(y)返回是“true”陪白,那么y.equals(x)也應(yīng)該返回是“true”颈走。
* 反射性:x.equals(x)必須返回是“true”。
* 類推性:如果x.equals(y)返回是“true”咱士,而且y.equals(z)返回是“true”立由,那么z.equals(x)也應(yīng)該返回是“true”轧钓。
* 還有一致性:如果x.equals(y)返回是“true”,只要x和y內(nèi)容一直不變锐膜,不管你重復(fù)x.equals(y)多少次毕箍,返回都是“true”。
* 任何情況下道盏,x.equals(null)而柑,永遠(yuǎn)返回是“false”;x.equals(和x不同類型的對(duì)象)永遠(yuǎn)返回是“false”荷逞。

hashCode():
這個(gè)函數(shù)返回的就是一個(gè)用來(lái)進(jìn)行赫希操作的整型代號(hào)媒咳,請(qǐng)不要把這個(gè)代號(hào)和前面所說(shuō)的參閱變量所代表的代號(hào)弄混了。后者不僅僅是個(gè)代號(hào)還具有在內(nèi)存中才查找對(duì)象的位置的功能种远。hashCode()所返回的值是用來(lái)分類對(duì)象在一些特定的收集對(duì)象中的位置涩澡。這些對(duì)象是HashMap, Hashtable, HashSet,等等坠敷。這個(gè)函數(shù)和上面的equals()函數(shù)必須自己設(shè)計(jì)妙同,用來(lái)協(xié)助HashMap, Hashtable, HashSet,等等對(duì)自己所收集的大量對(duì)象進(jìn)行搜尋和定位膝迎。

這些收集對(duì)象究竟如何工作的粥帚,想象每個(gè)元對(duì)象hashCode是一個(gè)箱子的編碼,按照編碼弄抬,每個(gè)元對(duì)象就是根據(jù)hashCode()提供的代號(hào)歸入相應(yīng)的箱子里茎辐。所有的箱子加起來(lái)就是一個(gè)HashSet宪郊,HashMap掂恕,或 Hashtable對(duì)象,我們需要尋找一個(gè)元對(duì)象時(shí)弛槐,先看它的代碼懊亡,就是hashCode()返回的整型值,這樣我們找到它所在的箱子乎串,然后在箱子里店枣,每個(gè)元對(duì)象都拿出來(lái)一個(gè)個(gè)和我們要找的對(duì)象進(jìn)行對(duì)比,如果兩個(gè)對(duì)象的內(nèi)容相等叹誉,我們的搜尋也就結(jié)束鸯两。這種操作需要兩個(gè)重要的信息,一是對(duì)象的 hashCode()长豁,還有一個(gè)是對(duì)象內(nèi)容對(duì)比的結(jié)果钧唐。

hashCode()的返回值和equals()的關(guān)系如下:

* 如果x.equals(y)返回“true”,那么x和y的hashCode()必須相等匠襟。
* 如果x.equals(y)返回“false”钝侠,那么x和y的hashCode()有可能相等该园,也有可能不等。

為什么這兩個(gè)規(guī)則是這樣的帅韧,原因其實(shí)很簡(jiǎn)單里初,拿HashSet來(lái)說(shuō)吧,HashSet可以擁有一個(gè)或更多的箱子忽舟,在同一個(gè)箱子中可以有一個(gè)或更多的獨(dú)特元對(duì)象(HashSet所容納的必須是獨(dú)特的元對(duì)象)双妨。這個(gè)例子說(shuō)明一個(gè)元對(duì)象可以和其他不同的元對(duì)象擁有相同的hashCode。但是一個(gè)元對(duì)象只能和擁有同樣內(nèi)容的元對(duì)象相等萧诫。所以這兩個(gè)規(guī)則必須成立斥难。
設(shè)計(jì)這兩個(gè)函數(shù)所要注意到的:
如果你設(shè)計(jì)的對(duì)象類型并不使用于收集性對(duì)象,那么沒(méi)有必要自己再設(shè)計(jì)這兩個(gè)函數(shù)的處理方式帘饶。這是正確的面向?qū)ο笤O(shè)計(jì)方法哑诊,任何用戶一時(shí)用不到的功能,就先不要設(shè)計(jì)及刻,以免給日后功能擴(kuò)展帶來(lái)麻煩镀裤。

如果你在設(shè)計(jì)時(shí)想別出心裁,不遵守以上的兩套規(guī)則缴饭,那么勸你還是不要做這樣想入非非的事暑劝。我還沒(méi)有遇到過(guò)哪一個(gè)開(kāi)發(fā)者和我說(shuō)設(shè)計(jì)這兩個(gè)函數(shù)要違背前面說(shuō)的兩個(gè)規(guī)則,我碰到這些違反規(guī)則的情況時(shí)颗搂,都是作為設(shè)計(jì)錯(cuò)誤處理担猛。

當(dāng)一個(gè)對(duì)象類型作為收集型對(duì)象的元對(duì)象時(shí),這個(gè)對(duì)象應(yīng)該擁有自己處理equals()丢氢,和/或處理hashCode()的設(shè)計(jì)傅联,而且要遵守前面所說(shuō)的兩種原則。equals()先要查null和是否是同一類型疚察。查同一類型是為了避免出現(xiàn)ClassCastException這樣的異常給丟出來(lái)蒸走。查 null是為了避免出現(xiàn)NullPointerException這樣的異常給丟出來(lái)。

如果你的對(duì)象里面容納的數(shù)據(jù)過(guò)多貌嫡,那么這兩個(gè)函數(shù) equals()和hashCode()將會(huì)變得效率低比驻。如果對(duì)象中擁有無(wú)法serialized的數(shù)據(jù),equals()有可能在操作中出現(xiàn)錯(cuò)誤岛抄。想象一個(gè)對(duì)象x别惦,它的一個(gè)整型數(shù)據(jù)是transient型(不能被serialize成二進(jìn)制數(shù)據(jù)流)。然而equals()和hashCode()都有依靠這個(gè)整型數(shù)據(jù)夫椭,那么掸掸,這個(gè)對(duì)象在serialization之前和之后,是否一樣益楼?答案是不一樣猾漫。因?yàn)閟erialization之前的整型數(shù)據(jù)是有效的數(shù)據(jù)点晴,在serialization之后,這個(gè)整型數(shù)據(jù)的值并沒(méi)有存儲(chǔ)下來(lái)悯周,再重新由二進(jìn)制數(shù)據(jù)流轉(zhuǎn)換成對(duì)象后粒督,兩者(對(duì)象在serialization 之前和之后)的狀態(tài)已經(jīng)不同了。這也是要注意的禽翼。
知道以上這些能夠幫助你:

  1. 進(jìn)行更好的設(shè)計(jì)和開(kāi)發(fā)屠橄。
  2. 進(jìn)行更好的測(cè)試案例開(kāi)發(fā)。
  3. 在面試過(guò)程中讓面試者對(duì)你的學(xué)識(shí)淵博感到滿意闰挡。
    hashCode的作用
      總的來(lái)說(shuō)锐墙,Java中的集合(Collection)有兩類,一類是List长酗,再有一類是 Set溪北。你知道它們的區(qū)別嗎?前者集合內(nèi)的元素是有序的夺脾,元素可以重復(fù)之拨;后者元素?zé)o序,但元素不可重復(fù)咧叭。那么這里就有一個(gè)比較嚴(yán)重的問(wèn)題了:要想保證元素不重復(fù)蚀乔,可兩個(gè)元素是否重復(fù)應(yīng)該依據(jù)什么來(lái)判斷呢?這就是Object.equals方法了菲茬。但是吉挣,如果每增加一個(gè)元素就檢查一次,那么當(dāng)元素很多時(shí)婉弹,后添加到集合中的元素比較的次數(shù)就非常多了睬魂。也就是說(shuō),如果集合中現(xiàn)在已經(jīng)有1000個(gè)元素马胧,那么第1001個(gè)元素加入集合時(shí)汉买,它就要調(diào)用1000次 equals方法衔峰。這顯然會(huì)大大降低效率佩脊。 于是,Java采用了哈希表的原理垫卤。哈希(Hash)實(shí)際上是個(gè)人名威彰,由于他提出一哈希算法的概念,所以就以他的名字命名了穴肘。哈希算法也稱為散列算法歇盼,是將數(shù)據(jù)依特定算法直接指定到一個(gè)地址上。如果詳細(xì)講解哈希算法评抚,那需要更多的文章篇幅豹缀,我在這里就不介紹了伯复。初學(xué)者可以這樣理解,hashCode方法實(shí)際上返回的就是對(duì)象存儲(chǔ)的物理地址(實(shí)際可能并不是)邢笙。 這樣一來(lái)啸如,當(dāng)集合要添加新的元素時(shí),先調(diào)用這個(gè)元素的hashCode方法氮惯,就一下子能定位到它應(yīng)該放置的物理位置上叮雳。如果這個(gè)位置上沒(méi)有元素,它就可以直接存儲(chǔ)在這個(gè)位置上妇汗,不用再進(jìn)行任何比較了帘不;如果這個(gè)位置上已經(jīng)有元素了,就調(diào)用它的equals方法與新元素進(jìn)行比較杨箭,相同的話就不存了寞焙,不相同就散列其它的地址。所以這里存在一個(gè)沖突解決的問(wèn)題互婿。這樣一來(lái)實(shí)際調(diào)用equals方法的次數(shù)就大大降低了棺弊,幾乎只需要一兩次。所以擒悬,Java對(duì)于eqauls方法和hashCode方法是這樣規(guī)定的:1模她、如果兩個(gè)對(duì)象相同,那么它們的hashCode值一定要相同懂牧;2侈净、如果兩個(gè)對(duì)象的hashCode相同,它們并不一定相同僧凤,上面說(shuō)的對(duì)象相同指的是用eqauls方法比較畜侦。你當(dāng)然可以不按要求去做了,但你會(huì)發(fā)現(xiàn)躯保,相同的對(duì)象可以出現(xiàn)在Set集合中旋膳。同時(shí),增加新元素的效率會(huì)大大下降途事。

我們知道验懊,equals()函數(shù)是用來(lái)做比較的。java中的比較有兩種:一種是內(nèi)存地址的比較尸变,一種是內(nèi)容的比較义图。而比較個(gè)體也有兩種:一種是簡(jiǎn)單類型(這類簡(jiǎn)單說(shuō)來(lái)無(wú)所謂內(nèi)存地址的比較或者內(nèi)容比較的區(qū)別);還有一種是對(duì)象的比較召烂,本文中說(shuō)的主要是后者

在java中碱工,(對(duì)象)內(nèi)存地址的比較,是通過(guò)==完成的。比如

這樣的語(yǔ)句中怕篷,我們認(rèn)為历筝,如果obj1和obj2的內(nèi)存地址相同,則返回true

而equals()通常是比較內(nèi)容的廊谓。這里說(shuō)“通陈龋” ,是因?yàn)樵谧罡镜腛bject類中蹂析,equal()函數(shù)做的是地址的比較舔示。而在其他幾乎所有的類中,equals()都經(jīng)過(guò)重載电抚,進(jìn)行內(nèi)容的比較惕稻。

而在說(shuō)equals()的時(shí)候我們還涉及hashCode()是因?yàn)樵谟行?yīng)用中(比如,HashMap的key是對(duì)象)蝙叛,必須在重載equals()的同時(shí)重載hashCode()俺祠。因?yàn)閖ava中默認(rèn)(Object)的hashCode是根據(jù)對(duì)象的地址計(jì)算得到的。

我們通常不會(huì)注意到這個(gè)問(wèn)題借帘,因?yàn)槲覀兺ǔK褂玫膋ey都是簡(jiǎn)單類型蜘渣,或者是String, Long等一些特殊的對(duì)象(其特殊性請(qǐng)參看筆者在寫(xiě)java 淺拷貝和深拷貝時(shí)的討論),這時(shí)候肺然,這個(gè)問(wèn)題被我們無(wú)意間繞過(guò)了

有人已經(jīng)概括了這種我們忽略了的情況:“如果你想將一個(gè)對(duì)象A放入另一個(gè)收集(集合)對(duì)象B里蔫缸,或者使用這個(gè)對(duì)象A為查找一個(gè)元對(duì)象在收集對(duì)象B里位置的鑰匙(key),并支持是否容納(isContains())际起,刪除收集對(duì)象B里的元對(duì)象(remove()?)這樣的操作拾碌,那么,equals()和hashCode()函數(shù)必須開(kāi)發(fā)者自己定義街望⌒O瑁” (括號(hào)為筆者添加)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市灾前,隨后出現(xiàn)的幾起案子防症,更是在濱河造成了極大的恐慌,老刑警劉巖哎甲,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔫敲,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡烧给,警方通過(guò)查閱死者的電腦和手機(jī)燕偶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)喝噪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)础嫡,“玉大人,你說(shuō)我怎么就攤上這事×穸Γ” “怎么了伯诬?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)巫财。 經(jīng)常有香客問(wèn)我盗似,道長(zhǎng),這世上最難降的妖魔是什么平项? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任赫舒,我火速辦了婚禮,結(jié)果婚禮上闽瓢,老公的妹妹穿的比我還像新娘接癌。我一直安慰自己,他們只是感情好扣讼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布缺猛。 她就那樣靜靜地躺著,像睡著了一般椭符。 火紅的嫁衣襯著肌膚如雪荔燎。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,365評(píng)論 1 302
  • 那天销钝,我揣著相機(jī)與錄音有咨,去河邊找鬼。 笑死蒸健,一個(gè)胖子當(dāng)著我的面吹牛摔吏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播纵装,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼征讲,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了橡娄?” 一聲冷哼從身側(cè)響起诗箍,我...
    開(kāi)封第一講書(shū)人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎挽唉,沒(méi)想到半個(gè)月后滤祖,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瓶籽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年匠童,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片塑顺。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡汤求,死狀恐怖俏险,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情扬绪,我是刑警寧澤竖独,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站挤牛,受9級(jí)特大地震影響莹痢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜墓赴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一竞膳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧诫硕,春花似錦顶猜、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至纲菌,卻和暖如春挠日,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背翰舌。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工嚣潜, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人椅贱。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓懂算,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親庇麦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子计技,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 本文出自 Eddy Wiki ,轉(zhuǎn)載請(qǐng)注明出處:http://eddy.wiki/interview-java.h...
    eddy_wiki閱讀 1,160評(píng)論 0 16
  • Java8張圖 11山橄、字符串不變性 12垮媒、equals()方法、hashCode()方法的區(qū)別 13航棱、...
    Miley_MOJIE閱讀 3,704評(píng)論 0 11
  • 在函數(shù)中定義的一些基本類型的變量和對(duì)象的引用變量都在函數(shù)的棧內(nèi)存中分配睡雇。 當(dāng)在一段代碼塊定義一個(gè)變量時(shí),Java就...
    木有魚(yú)丸啦閱讀 553評(píng)論 0 0
  • 1 概述 這三者怎么聯(lián)系起來(lái)的观蓄,有交叉--混移。 ==關(guān)系運(yùn)算符關(guān)系運(yùn)算符。 equals()和hashCode()是...
    凱玲之戀閱讀 542評(píng)論 0 1
  • 1蜘腌、瀝干水的老豆腐切成小塊沫屡,操作過(guò)程中忌油饵隙、忌鹽撮珠,手需溫水沖洗干凈 2、在菜市場(chǎng)買(mǎi)菌絲細(xì)長(zhǎng)的霉豆渣或霉千張金矛,將切好...
    影音紀(jì)事閱讀 1,310評(píng)論 0 0