本篇文章6735字,大概閱讀時(shí)間20分鐘。本文中使用到的JDK版本為1.8.0_301
目錄
==符號(hào)的定義
基本類型中==符號(hào)的判斷
String類型中==符號(hào)的判斷
==符號(hào)的定義
? ? ? ? 在Java中==符號(hào)的作用分為兩類:
? ? ? ? 1:==符號(hào)在八種基本類型的作用是比較對(duì)應(yīng)基本類型的數(shù)值是否相等
? ? ? ? 2:==符號(hào)在對(duì)象類型的作用是比較兩個(gè)對(duì)象是否相等
? ? ? ? 在對(duì)象類型中又有兩類特殊情況,一種是基本類型中包裝類對(duì)象,一種是String對(duì)象。前者由于存在緩存導(dǎo)致,后綴則是有字符串常量池的存在導(dǎo)致。
基本類型中==符號(hào)的判斷
????????在基本類型中==符號(hào)的作用是判斷基本類型的數(shù)值是否相同
int i1 = 1;
int i2 = 2;
int i3 = 1;
System.out.println(i1 == i2); //false
System.out.println(i1 == i3); //true
char c1 = 'a';
char c2 = 'b';
char c3 = 'a';
System.out.println(c1 == c2); //false
System.out.println(c1 == c3); //true
? ? ? ? 在基本類型的包裝類中由于存在緩存以及自動(dòng)拆箱/裝箱,導(dǎo)致==符號(hào)的使用存在一定的差異,下面將會(huì)解釋到。
? ? ? ? 首先基本類型和包裝類對(duì)應(yīng)關(guān)系
基本類型 | 包裝類型 |
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
? ? ? ? 在看一段代碼(建議將代碼復(fù)制粘貼到別處,下文會(huì)經(jīng)常提到這段代碼中的內(nèi)容)
Integer i1 = 127; //數(shù)值127 自動(dòng)裝箱
Integer i2 = 127; //數(shù)值127 自動(dòng)裝箱
System.out.println(i1 == i2); //true
Integer i3 = 128; //數(shù)值128 自動(dòng)裝箱
Integer i4 = 128; //數(shù)值128 自動(dòng)裝箱
System.out.println(i3 == i4); //false
Integer i5 = new Integer(127);
Integer i6 = new Integer(127);
System.out.println(i5 == i6); //false
Integer i7 = new Integer(128);
Integer i8 = new Integer(128);
System.out.println(i7 == i8); //false
int i9 = 127;
System.out.println(i1 == i9); // true i1自動(dòng)拆箱
System.out.println(i5 == i9); // true i9自動(dòng)拆箱
? ? ? ? 首先i1==i2(true)和i3==i4(false)?這兩種情況,要解釋這兩種情況,先看看自動(dòng)拆箱/裝箱背后的邏輯。將上面這段代碼編譯成Class文件,然后通過javap命令進(jìn)行反編譯,查看字節(jié)碼的執(zhí)行邏輯?。
????????javap是jdk自帶的反解析工具。它的作用就是根據(jù)class字節(jié)碼文件,反解析出當(dāng)前類對(duì)應(yīng)的code區(qū) (字節(jié)碼指令)、局部變量表、異常表和代碼行偏移量映射表、常量池等信息。
????????? 通過執(zhí)行 javap -c /xx.class?命令,輸出下面內(nèi)容
????????在輸出內(nèi)容中,可以看到反匯編后,代碼Integer i1 = 127在=號(hào)右邊的127會(huì)調(diào)用Integer.valueOf(127)方法,將數(shù)值127包裝成Integer(127)的Integer對(duì)象,同理i2、i3、i4也是相同原理。通過上述內(nèi)容可以看出,自動(dòng)裝箱就是調(diào)用包裝類的valueOf()方法將基本類型的值封裝成對(duì)應(yīng)的包裝類對(duì)象。
? ? ? ? 現(xiàn)在我們?nèi)タ匆幌翴nteger.valueOf方法具體做了哪些事情
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
? ? ? ? 在看一下IntegerCache的代碼
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h; //127
//創(chuàng)建一個(gè)Integer類型的cache數(shù)組長度為256
cache = new Integer[(high - low) + 1];
int j = low;
//從下標(biāo)0開始依次存入-128 -127 .... 126 127
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
? ? ? ? 在valueOf()方法中首先會(huì)判斷傳入的i在不在-128~127之間,如果在這區(qū)間就會(huì)調(diào)用 IntegerCache.cache,而cache是一個(gè)Integer對(duì)象的數(shù)組,并且是在static代碼塊中進(jìn)行初始化,在IntegerCache對(duì)象初始化完成后,cache數(shù)組已經(jīng)初始化完畢。所以如果在-128~127之間,直接返回cache中已經(jīng)創(chuàng)建好的Integer對(duì)象,如果不在-128~127之間則會(huì)重新new一個(gè)Integer對(duì)象。
? ? ? ? 通過上面的分析就可以得出 i1=127和i2=127在進(jìn)行自動(dòng)裝箱的時(shí)候,通過valueOf方法返回的都是同一個(gè)已經(jīng)在cache中存在的Integer(127)對(duì)象,而i3=128和i4=128則是直接通過new的方式創(chuàng)建的兩個(gè)不同的Integer對(duì)象。而==符號(hào)在比較兩個(gè)對(duì)象的時(shí)候是判斷兩個(gè)對(duì)象是否相等,明顯i1和i2都是cache緩存中的同一個(gè)對(duì)象所以==比較結(jié)果為true,而i3和i4是兩個(gè)不同的對(duì)象,所以==比較的結(jié)果是false。
? ? ? ? 注意:通過上面的分析,在包裝類比較數(shù)值是否相等的時(shí)候需要使用equals方法進(jìn)行,不要使用==進(jìn)行比較數(shù)值是否相等,由于有緩存的存在可能導(dǎo)致結(jié)果出現(xiàn)差異。
? ? ? ? 繼續(xù)看上面的代碼,在?i5、i6、i7、i8都是創(chuàng)建的不同的Integer對(duì)象,所以使用==符號(hào)的時(shí)候比較的結(jié)果都是false。
? ? ? ? 在i9處定義了一個(gè)數(shù)值為127的常量,i1==i9和i5==i9比較的時(shí)候,我們繼續(xù)查看反編譯? ? ??????????在i1==i9和i5==i9比較的時(shí)候 i1和i5會(huì)調(diào)用intValue()方法,獲取Integer對(duì)象中存儲(chǔ)的常量值
public int intValue() {return value;}
????????所以i1==i9和i5==i9比較的是數(shù)值大小,在基本類型和包裝類型進(jìn)行比較的時(shí)候,包裝類型通過調(diào)用xxxValue()方法獲取對(duì)應(yīng)的常量值,這個(gè)就是自動(dòng)拆箱。?
????????總結(jié):
? ? ? ? 1:在引用類型是包裝類型的時(shí)候,而等號(hào)右邊是基本類型的字面量,類似Integer i = 127的時(shí)候,會(huì)觸發(fā)自動(dòng)裝箱,調(diào)用對(duì)應(yīng)的valueOf()方法將字面量封裝成包裝類型。
? ? ? ? 2:在基本類型的字面量和包裝類對(duì)象比較的時(shí)候,包裝類對(duì)象會(huì)調(diào)用對(duì)應(yīng)xxxValue()方法,獲取對(duì)象中的字面量值,所以比較的是字面量的值。
? ? ? ? 3:基本類型的自動(dòng)裝箱就是調(diào)用對(duì)應(yīng)包裝類的valueOf()方法,而自動(dòng)拆箱就是包裝類調(diào)用xxxValue()方法。
? ? ? ? 上面介紹的時(shí)候是使用int和int的包裝類Integer作為例子,下面將會(huì)羅列出八種基本類型全部情況。?
基本類型 | 包裝類型 | 緩存(valueOf方法) |
byte | Byte | 存在緩存 -128~127之間 |
short | Short | 存在緩存 -128~127之間 |
int | Integer | 存在緩存 -128~127之間 |
long | Long | 存在緩存 -128~127之間 |
float | Float | 沒有緩存 |
double | Double | 沒有緩存 |
char | Character | 存在緩存 0~127之間 |
boolean | Boolean | 存在緩存 true/false |
String類型中==符號(hào)的判斷
? ? ? ? 本文所用的JDK版本為1.8.0_301,在不同JDK版本中字符串常量池存在差異,所以接下來代碼的運(yùn)行環(huán)境都是1.8.0_301版本。
? ? ? ? 在JDK8中字符串常量池的定義為:字符串常量池在堆中存儲(chǔ)
? ? ? ? 先看下面代碼
String s1 = "abc";
String s2 = new String("abc");
? ? ? ? 通過javap命令進(jìn)行反編譯
? ? ? ? 上圖中l(wèi)dc、astore這些JVM指令的解釋可以查看這里,使用頁面搜索來查找
? ? ? ? 解讀反編譯后的內(nèi)容
? ? ? ? Code 0:ldc指令將"abc"字面量存儲(chǔ)到字符串常量池中,也就是String s1 = "abc"。如果常量池中不存在則需要在常量池中創(chuàng)建,如果常量池中有則直接使用。
? ? ? ? Code 2:將s1引用保存到局部變量表中
? ? ? ? Code 3:new一個(gè)字符串對(duì)象,對(duì)應(yīng)的是String s2 = new String("abc")
? ? ? ? Code 6:將創(chuàng)建的對(duì)象s2壓入操作數(shù)棧中
? ? ? ? Code 7:由于s2中的字符串"abc"已經(jīng)存在在字符串常量池中,所以不需要再次創(chuàng)建"abc",直接從常量池中獲取
? ? ? ? Code 9:初始化s2對(duì)象,調(diào)用String(str String)的構(gòu)造方法,將已經(jīng)存在常量池中的"abc"字符串傳入構(gòu)造方法,完成s2對(duì)象的創(chuàng)建
? ? ? ? Code 12:將s2引用保存到局部變量表中
?????????再將上面一段代碼,順序顛倒
String s2 = new String("abc");
String s1 = "abc";
? ? ? ? 再通過javap命令進(jìn)行反編譯
????????解讀反編譯后的內(nèi)容
? ? ? ? Code 0:new一個(gè)字符串對(duì)象,對(duì)應(yīng)的是String s2 = new String("abc")
? ? ? ? Code 3:將創(chuàng)建的對(duì)象s2壓入操作數(shù)棧中
? ? ? ? Code 4:ldc指令將new String("abc")中的"abc"字面量存儲(chǔ)到字符串常量池中,如果常量池中不存在則需要在常量池中創(chuàng)建,如果常量池中有則直接使用。這里"abc"由于在常量池中不存在,則第一次創(chuàng)建。
? ? ? ? Code 6:初始化s2對(duì)象,調(diào)用String(str String)的構(gòu)造方法,將已經(jīng)存在常量池中的"abc"字符串傳入構(gòu)造方法,完成s2對(duì)象的創(chuàng)建
? ? ? ? Code 9:將s2引用保存到局部變量表中
? ? ? ? Code 10:創(chuàng)建String s1 = "abc",由于"abc"已經(jīng)在常量池中存在,則無需再次創(chuàng)建,直接使用常量池中的"abc"。
? ? ? ? Code 12:將創(chuàng)建的對(duì)象s1壓入操作數(shù)棧中?
?? ? ? ? 通過上面的分析,總結(jié)如下:
? ? ? ? 1:jdk8中字符串常量池存是在堆中創(chuàng)建一塊區(qū)域進(jìn)行存儲(chǔ)。
? ? ? ? 2:字符串字面值的創(chuàng)建是直接在字符串常量池中進(jìn)行創(chuàng)建,在創(chuàng)建前會(huì)檢查常量池中是否已經(jīng)存在對(duì)應(yīng)的字符串,如果不存在則在常量池中進(jìn)行創(chuàng)建,存在則直接使用。
? ? ? ? 3:如果通過new 來創(chuàng)建字符串對(duì)象,首先會(huì)判斷字面值是否在常量池中已經(jīng)存在,如果不存在則在常量池中進(jìn)行創(chuàng)建,存在則直接使用。然后將常量池中的字符串引用傳遞給在堆中創(chuàng)建的字符串對(duì)象,再將堆中創(chuàng)建的字符串對(duì)象傳遞給s2。
? ? ? ? 通過上面的分析還可得出一個(gè)常見的面試問題String s2 = new String("abc"),s2的創(chuàng)建過程一共創(chuàng)建了幾次對(duì)象?
? ? ? ? 答案是一次或者兩次
? ? ? ? 一次:類似第一種情況,常量池中已經(jīng)存在了對(duì)應(yīng)的"abc"字符串,所以只需要在堆中創(chuàng)建new String("abc")對(duì)象即可,然后將堆中的對(duì)象指向s2。
? ? ? ? 兩次:類似第二種情況,常量池中不存在,則首先要在常量池中創(chuàng)建一次"abc"字符串對(duì)象,然后再在堆中第二次創(chuàng)建new String("abc")對(duì)象,并且把常量池中的對(duì)象指向堆中的對(duì)象,然后堆中的對(duì)象再指向s2。
? ? ? ? 通過上面的分析,接下來可以研究==符號(hào)在字符串中的判斷
String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
String s4 = new String("abc");
System.out.println(s1 == s2); //true
System.out.println(s1 == s3); //false
System.out.println(s3 == s4); //false
? ? ? ? s1和s2都是在常量池中創(chuàng)建的,并且在s2創(chuàng)建的時(shí)候"abc"字符串已經(jīng)存在,所以s2得引用指向的是s1,故s1==s2為true,而s3和s4都是在堆上創(chuàng)建的對(duì)象,雖然s3和s4中字符串指向的是常量池中的同一個(gè)"abc",但是s3和s4是堆上的不同對(duì)象所以 s1==s3和s3==s4為false
????????下面在分析幾種使用+拼接字符串的情況下==符號(hào)的判斷
? ? ? ? 在判斷之前先熟悉兩個(gè)概念
? ? ? ??常量折疊優(yōu)化:是指Java在編譯期做的一個(gè)優(yōu)化,將一些表達(dá)式在編譯期能計(jì)算好的,不用放到運(yùn)行期間進(jìn)行計(jì)算
? ? ? ? 例如? 1+1或者 "ab" +"c"這些可以在編譯期直接計(jì)算出表達(dá)式的結(jié)果 2和"abc"。
? ? ? ? 字面量:指直接寫在代碼中的數(shù)字、字符串或布爾值。例如數(shù)值123 123.11或者字符串"abc"直接在代碼中定義的值。
? ? ? ? 接下來看+號(hào)拼接字符串的情況下==符號(hào)的判斷
? ? ? ? 第一種情況:常量折疊優(yōu)化
String s1 = "abc";
String s2 = "a" + "b" + "c";
System.out.println(s1 == s2); //true
? ? ? ? 在這種情況下 s2在編譯期間會(huì)進(jìn)行常量折疊優(yōu)化,直接拼接成"abc"。
? ? ? ? 第二種情況:拼接字符串中有一個(gè)是變量或是通過
new
關(guān)鍵字實(shí)例化的對(duì)象
String s1 = "abc";
String s2 = "ab";
String s3 = s2 + "c";
String s4 = new String("ab") + "c";
String s5 = new String("ab") + new String("c");
System.out.println(s1 == s3); //false
System.out.println(s1 == s4); //false
System.out.println(s1 == s5); //false
? ? ? ? 這種情況下,由于無法再編譯器確定對(duì)象的值,只能在運(yùn)行期間確定,s3、s4、s5都是在堆上創(chuàng)建的不同的字符串對(duì)象,所以比較結(jié)果都是false。? ??
? ? ? ? ?第三種情況:final
修飾的字面量實(shí)例化的字符串編譯器在編譯前也可以直接確認(rèn)它的值。
String s1 = "abc";
final String s2 = "ab";
String s3 = s2 + "c";
String s4 = s2 + new String("c");
System.out.println(s1 == s3); //true
System.out.println(s1 == s4); //false
? ? ? ? s2是被final修飾的常量,其值不會(huì)發(fā)生改變。在s3=s2+"c",可以看成 s3="ab"+"c"。所以在上面的判斷中s1==s3為true。?而s4中雖然s2是常量帶時(shí)候+后面跟著一個(gè)變量,所以在堆中創(chuàng)建了s4,故s1==s4為false。
? ? ? ? 通過上面的分析,我們可以得出如下結(jié)論:
? ? ? ? 1:字符串的字面量都是存在在常量池中,并且如果字面量在常量池中已經(jīng)存在,則不會(huì)再次創(chuàng)建。
? ? ? ? 2: 由于有常量折疊優(yōu)化的存在,字符串的字面量在使用+號(hào)拼接的時(shí)候可以在編譯期確定其值
? ? ? ? 3:+號(hào)拼接字符串中有一個(gè)是變量或是通過new
關(guān)鍵字實(shí)例化的對(duì)象,都是會(huì)在堆中創(chuàng)建其對(duì)象
? ? ? ? 4:final
修飾的字面量實(shí)例化的字符串編譯器在編譯前也可以直接確認(rèn)它的值。
? ? ? ? 還有在字符串需要比較值是否相等的時(shí)候,需要使用equals()方法。并且equals方法的左邊最好是確定不為空或者字符串的字面值。防止出現(xiàn)NullPointException異常。
? ? ? ? 結(jié)束語:
? ? ? ? 本文從八大基本類型和其包裝類,以及String對(duì)象分析==字符在Java中的應(yīng)用判斷情況,相信通過本篇文章的閱讀,可以使你如同文章標(biāo)題一樣,全面搞定==符號(hào)。當(dāng)然這也是我寫這篇文章的目的,感謝您能看到最后。
????????方便自己,也方便他人,堅(jiān)持原創(chuàng)!文章來源:http://www.zghlxwxcb.cn/news/detail-715575.html
????????文章來源地址http://www.zghlxwxcb.cn/news/detail-715575.html
到了這里,關(guān)于一篇文章帶你徹底弄懂Java的==符號(hào)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!