JDK8中String的intern()方法實(shí)例詳細(xì)解讀
一、前言
String字符串在我們?nèi)粘i_(kāi)發(fā)中最常用的,當(dāng)然還有他的兩個(gè)兄弟StringBuilder和StringBuilder。他三個(gè)的區(qū)別也是面試中經(jīng)常問(wèn)到的,大家如果不知道,就要先去看看了哈!最近也是看周志明老師的深入JVM一書(shū)中寫(xiě)到關(guān)于intern()方法的介紹,小編也是以前沒(méi)在開(kāi)發(fā)中用到。但是面試題還是很多的,所以特意研究了一天,寫(xiě)下來(lái)記錄一下自己的收獲,希望也可以幫助到大家?。?/p>
二、圖文理解String創(chuàng)建對(duì)象
1.例子一
String str1 = "wang";
JVM在編譯階段會(huì)判斷字符串常量池中是否有 "wang" 這個(gè)常量對(duì)象如果有,str1直接指向這個(gè)常量的引用,如果沒(méi)有會(huì)在常量池里創(chuàng)建這個(gè)常量對(duì)象。

2.例子二
String str2 = "學(xué)" + "Java";
JVM編譯階段過(guò)編譯器優(yōu)化后會(huì)把字符串常量直接合并成"學(xué)Java",所有創(chuàng)建對(duì)象時(shí)只會(huì)在常量池中創(chuàng)建1個(gè)對(duì)象。

3.例子三
String str3 = new String("學(xué)Java");當(dāng)代碼執(zhí)行到括號(hào)中的"學(xué)Java"的時(shí)候會(huì)檢測(cè)常量池中是否存在"學(xué)Java"這個(gè)對(duì)象,如果不存在則在字符串常量池中創(chuàng)建一個(gè)對(duì)象。當(dāng)整行代碼執(zhí)行完畢時(shí)會(huì)因?yàn)閚ew關(guān)鍵字在堆中創(chuàng)建一個(gè)"學(xué)Java"對(duì)象,并把棧中的變量"str3"指向堆中的對(duì)象,如下圖所示。這也是為什么說(shuō)通過(guò)new關(guān)鍵字在大部分情況下會(huì)創(chuàng)建出兩個(gè)字符串對(duì)象!

4.例子四
String str4 = "學(xué)Java"; String str5 = "學(xué)Java"; System.out.println(str4 == str5); // 如下圖得知為:true
第一行代碼:
JVM在編譯階段會(huì)判斷字符串常量池中是否有 "學(xué)Java" 這個(gè)常量對(duì)象如果有,str4直接指向這個(gè)常量的引用,如果沒(méi)有會(huì)在常量池里創(chuàng)建這個(gè)常量對(duì)象。
第二行代碼:
再創(chuàng)建"學(xué)Java",發(fā)現(xiàn)字符串常量池中存在了"學(xué)Java",所以直接將棧中的str5變量也指向字符串常量池中已存在的"學(xué)Java"對(duì)象,從而避免重復(fù)創(chuàng)建對(duì)象,這也是字符串常量池存在的原因。

5.例子五
String str6 = new String("學(xué)") + new String("Java");
首先,會(huì)先判斷字符串常量池中是否存在"學(xué)"字符串對(duì)象,如果不存在則在字符串常量池中創(chuàng)建一個(gè)對(duì)象。當(dāng)執(zhí)行到new關(guān)鍵字在堆中創(chuàng)建一個(gè)"學(xué)"字符串對(duì)象。后面的new String("Java"),也是這樣。
然后,當(dāng)右邊完成時(shí),會(huì)在堆中創(chuàng)建一個(gè)"學(xué)Java"字符串對(duì)象。并把棧中的變量"str6"指向堆中的對(duì)象。
總結(jié):一句代碼創(chuàng)建了5個(gè)對(duì)象,但是有兩個(gè)在堆中是沒(méi)有引用的,按照垃圾回收的可達(dá)性分析,他們是垃圾就是"學(xué)"、"Java"這倆垃圾。
心得:
上面代碼進(jìn)行反編譯:
String str6 = (new StringBuilder()).append(new String("\u5B66"))
.append(new String("Java")).toString();底層是一個(gè)StringBuilder在進(jìn)行把兩個(gè)對(duì)象拼接在一起,最后棧中str6指向堆中的"學(xué)Java",其實(shí)是StringBuilder對(duì)象。

6.例子六
String str7 = new String("學(xué)Java");
String str8 = new String("學(xué)Java");
System.out.println(str7 == str8); // 如下圖得知為:false執(zhí)行到第一行:
執(zhí)行到括號(hào)內(nèi)的"學(xué)Java",會(huì)先判斷字符串常量池中是否存在"學(xué)Java"字符串對(duì)象,如果沒(méi)有則在字符串常量池中創(chuàng)建一個(gè)"學(xué)Java"字符串對(duì)象,執(zhí)行到new關(guān)鍵字時(shí),在堆中創(chuàng)建一個(gè)"學(xué)Java"字符串對(duì)象,棧中的變量str7的引用指向堆中的"學(xué)Java"字符串對(duì)象。
執(zhí)行到第二行:
當(dāng)執(zhí)行到第二行括號(hào)中的"學(xué)Java"時(shí),先判斷常量池中是否有"學(xué)Java"字符串對(duì)象,因?yàn)榈谝恍写a已經(jīng)將其創(chuàng)建,所以有的話就不創(chuàng)建了;執(zhí)行到new關(guān)鍵字時(shí),在堆中創(chuàng)建一個(gè)"學(xué)Java"字符串對(duì)象,棧中的變量str8的引用指向堆中的"學(xué)Java"字符串對(duì)象。

三、深入理解intern()方法
1. 源碼查看
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
// ....
/**
* Returns a canonical representation for the string object.
* <p>
* A pool of strings, initially empty, is maintained privately by the
* class {@code String}.
* <p>
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
* <p>
* It follows that for any two strings {@code s} and {@code t},
* {@code s.intern() == t.intern()} is {@code true}
* if and only if {@code s.equals(t)} is {@code true}.
* <p>
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section 3.10.5 of the
* <cite>The Java™ Language Specification</cite>.
*
* @return a string that has the same contents as this string, but is
* guaranteed to be from a pool of unique strings.
*/
public native String intern();
}
翻譯過(guò)來(lái)就是,當(dāng)intern()方法被調(diào)用的時(shí)候,如果字符串常量池中已經(jīng)存在這個(gè)字符串對(duì)象了,就返回常量池中該字符串對(duì)象的地址;如果字符串常量池中不存在,就在常量池中創(chuàng)建一個(gè)指向該對(duì)象堆中實(shí)例的引用,并返回這個(gè)引用地址。
2. 例子一
我們直接先把周志明老師的在深入JVM一書(shū)的例子:
String str1 = new StringBuilder("計(jì)算機(jī)").append("軟件").toString();
System.out.println(str1.intern() == str1);
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern() == str2);這段代碼在JDK 6中運(yùn)行,會(huì)得到兩個(gè)false,而在JDK 7、8中運(yùn)行,會(huì)得到一個(gè)true和一個(gè)false。產(chǎn) 生差異的原因是,在JDK 6中,intern()方法會(huì)把首次遇到的字符串實(shí)例復(fù)制到永久代的字符串常量池 中存儲(chǔ),返回的也是永久代里面這個(gè)字符串實(shí)例的引用,而由StringBuilder創(chuàng)建的字符串對(duì)象實(shí)例在 Java堆上,所以必然不可能是同一個(gè)引用,結(jié)果將返回false。 而JDK 7(以及部分其他虛擬機(jī),例如JRockit)的intern()方法實(shí)現(xiàn)就不需要再拷貝字符串的實(shí)例到永久代了,既然字符串常量池已經(jīng)移到Java堆中,那只需要在常量池里記錄一下首次出現(xiàn)的實(shí)例引用即可,因此intern()返回的引用和由StringBuilder創(chuàng)建的那個(gè)字符串實(shí)例就是同一個(gè)。而對(duì)str2比較返 回false,這是因?yàn)?ldquo;java”(下面解釋?zhuān)┻@個(gè)字符串在執(zhí)行String-Builder.toString()之前就已經(jīng)出現(xiàn)過(guò)了,字符串常量 池中已經(jīng)有它的引用,不符合intern()方法要求“首次遇到”的原則,“計(jì)算機(jī)軟件”這個(gè)字符串則是首次出現(xiàn)的,因此結(jié)果返回true。

java為什么已經(jīng)存在了?
1.我們?cè)谝粋€(gè)類(lèi)中輸入System,然后點(diǎn)擊到這個(gè)方法中,方法內(nèi)容如下:
public final class System {
// ...
private static void initializeSystemClass() {
// ...
sun.misc.Version.init();
// ...
}
// ...
}2.我們點(diǎn)擊上面的Version類(lèi),類(lèi)內(nèi)容如下:
public class Version {
private static final String launcher_name = "java";
private static final String java_version = "1.8.0_121";
private static final String java_runtime_name = "Java(TM) SE Runtime Environment";
private static final String java_profile_name = "";
private static final String java_runtime_version = "1.8.0_121-b13";
private static boolean versionsInitialized;
private static int jvm_major_version;
private static int jvm_minor_version;
private static int jvm_micro_version;
private static int jvm_update_version;
private static int jvm_build_number;
private static String jvm_special_version;
private static int jdk_major_version;
private static int jdk_minor_version;
private static int jdk_micro_version;
private static int jdk_update_version;
private static int jdk_build_number;
private static String jdk_special_version;
private static boolean jvmVersionInfoAvailable;
public Version() {
}
public static void init() {
System.setProperty("java.version", "1.8.0_121");
System.setProperty("java.runtime.version", "1.8.0_121-b13");
System.setProperty("java.runtime.name", "Java(TM) SE Runtime Environment");
}
}3.找到j(luò)ava關(guān)鍵字,所以上面的str2.intern() == str2返回false。
private static final String launcher_name = "java";
我們開(kāi)始例子和詳細(xì)解釋?zhuān)l(fā)車(chē)了,大家坐好哦!
以下例子來(lái)自:原博客,解釋是為小編自己的理解。
3. 例子二
String str1 = new String("wang");
str1.intern();
String str2 = "wang";
System.out.println(str1 == str2); // false執(zhí)行第一行代碼:
首先執(zhí)行到"wang",因?yàn)樽址A砍刂袥](méi)有,則會(huì)在字符串常量池中創(chuàng)建"wang"字符串對(duì)象。
然后執(zhí)行到new關(guān)鍵字時(shí),在堆中創(chuàng)建一個(gè)"wang"的對(duì)象,并把棧中的str1的引用指向"wang"對(duì)象。
執(zhí)行第二行代碼:
這里我們看到就是str1手動(dòng)把"wang"放在字符串常量池中,但是發(fā)現(xiàn)字符串常量池中已經(jīng)存在"wang"字符串對(duì)象,所以直接把已存在的引用返回。雖然str1.intern()指向了字符串常量池中的"wang",但是我們第四行代碼并沒(méi)有拿str1.intern()作比較,所以還是false。
執(zhí)行第三行代碼:
首先通過(guò)第一行代碼,字符串常量池中已經(jīng)有"wang"字符串對(duì)象了,所以本行代碼只需要把棧中的str2變量指向字符串常量池中的"wang"即可。
執(zhí)行第四行代碼:
如上和下圖可見(jiàn),我們的str1執(zhí)行堆中的"wang",str2指向的是字符串常量池中的"wang",肯定返回false。

4. 例子三
String str3 = new String("wang") + new String("zhen");
str3.intern();
String str4 = "wangzhen";
System.out.println(str3 == str4); // true執(zhí)行到第一行代碼:
首先執(zhí)行到"wang"時(shí),因?yàn)樽址A砍刂袥](méi)有,則會(huì)在字符串常量池中創(chuàng)建一個(gè)"wang"字符串對(duì)象;
然后執(zhí)行到"zhen"時(shí),因?yàn)樽址A砍刂袥](méi)有,則會(huì)在字符串常量池中創(chuàng)建一個(gè)"zhen"字符串對(duì)象;
最后執(zhí)行到new關(guān)鍵字時(shí),看到是兩個(gè),但是底層字節(jié)碼文件反編譯的是使用如下可見(jiàn),只會(huì)有一個(gè)StringBuilder對(duì)象生成,于是將棧中的str3的引用指向堆中的"wangzhen"對(duì)象。
String str3 = (new StringBuilder()).append(new String("wang"))
.append(new String("zhen")).toString();執(zhí)行到第二行代碼:
這里我們看到就是str3手動(dòng)把"wangzhen"放在字符串常量池中,在字符串常量池中沒(méi)有找到"wangzhen",于是把str3 .intern()引用指向堆中的"wangzhen"的地址?,F(xiàn)在str3和str3 .intern()一樣
執(zhí)行到第三行代碼:
判斷字符串常量池中是否存在"wangzhen"字符串對(duì)象,第二行代碼已經(jīng)在字符串常量池中創(chuàng)建了"wangzhen",不過(guò)str4是指向str3中堆的引用(看圖就明白了)。
執(zhí)行到第四行代碼:
str3和str3 .intern()引用一樣,str3 .intern()和str4是一個(gè),所以str3和str4相等。

5. 例子四
String str5 = new String("wang") + new String("zhen");
String str6 = "wangzhen";
str5.intern();
System.out.println(str5 == str6); // false執(zhí)行到第一行代碼:
首先執(zhí)行到"wang"時(shí),因?yàn)樽址A砍刂袥](méi)有,則會(huì)在字符串常量池中創(chuàng)建一個(gè)"wang"字符串對(duì)象;
然后執(zhí)行到"zhen"時(shí),因?yàn)樽址A砍刂袥](méi)有,則會(huì)在字符串常量池中創(chuàng)建一個(gè)"zhen"字符串對(duì)象;
最后執(zhí)行到new關(guān)鍵字時(shí),看到是兩個(gè),但是底層字節(jié)碼文件反編譯的是使用如下可見(jiàn),只會(huì)有一個(gè)StringBuilder對(duì)象生成,于是將棧中的str5的引用指向堆中的"wangzhen"對(duì)象。同上面的反編譯代碼
執(zhí)行到第二行代碼:
執(zhí)行到"wangzhen",判斷字符串常量池中是否存在"wangzhen",發(fā)現(xiàn)沒(méi)有,在字符串常量池中創(chuàng)建"wangzhen"字符串對(duì)象,然后把棧中的str6變量的引用指向"wangzhen"對(duì)象。
執(zhí)行到第三行代碼:
這里我們看到就是str5手動(dòng)把"wangzhen"放在字符串常量池中,我們發(fā)現(xiàn),在字符串常量池中已存在"wangzhen",于是str5 .intern()就是"wangzhen"對(duì)象的地址。我們還沒(méi)沒(méi)有收到返回值
如下圖,我們看到肯定返回false,此時(shí)str5.intern() == str6 (true)

6. 例子五
String str7 = new String("wang") + new String("zhen");
String str8 = "wangzhen";
System.out.println(str7.intern() == str8); // true
System.out.println(str7 == str8); // false
System.out.println(str8 == "wangzhen"); // true執(zhí)行到第一行代碼:
同例子三和例子四的第一行代碼;
執(zhí)行到第二行代碼:
先判斷字符串常量池中是否存在"wangzhen"對(duì)象,發(fā)現(xiàn)沒(méi)有,我們?cè)谧址A砍刂袆?chuàng)建"wangzhen"字符串對(duì)象;
執(zhí)行到第三行代碼:
執(zhí)行到str7.intern()這里,我們看到就是str7手動(dòng)把"wangzhen"放在字符串常量池中,在字符串常量池中已結(jié)存在"wangzhen",于是把字符串常量池"wangzhen"的地址。現(xiàn)在str7和str7 .intern()一樣
執(zhí)行到第四行代碼:
str7的指向?yàn)槎阎械?quot;wangzhen",而str8指向則為字符串常量池中的"wangzhen",故不相同,返回false。
執(zhí)行到第五行代碼:
str8指向則為字符串常量池中的"wangzhen",執(zhí)行"wangzhen",則把已存在的字符串常量池中"wangzhen"返回,故相同,返回true。

7. 例子六
String str9 = new String("wang") + new String("zhen");
System.out.println(str9.intern() == str9); // true
System.out.println(str9 == "wangzhen"); // true執(zhí)行到第一行代碼:
同上
執(zhí)行到第二行代碼:
執(zhí)行到str9.intern()這里,我們看到就是str9手動(dòng)把"wangzhen"放在字符串常量池中,在字符串常量池中沒(méi)有"wangzhen",于是把str3 .intern()引用指向堆中的"wangzhen"的地址?,F(xiàn)在str9和str9.intern()一樣
執(zhí)行到第三行代碼:
str9指向堆內(nèi)存中的"wangzhen",執(zhí)行到"wangzhen"時(shí),發(fā)現(xiàn)字符串常量池中已存在,直接返回str9指向的引用即可,故返回true。

四、總結(jié)
經(jīng)過(guò)這么多例子,大家應(yīng)該明白了吧,還是要自己跟著例子進(jìn)行換一下jvm內(nèi)存圖,這樣就理解記憶,也不會(huì)輕易忘記!
到此這篇關(guān)于JDK8中String的intern()方法詳細(xì)解讀的文章就介紹到這了,更多相關(guān)JDK8中String的intern()內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
簡(jiǎn)單了解SpringMVC與Struts2的區(qū)別
這篇文章主要介紹了簡(jiǎn)單了解SpringMVC與Struts2的區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
springboot實(shí)現(xiàn)多實(shí)例crontab搶占定時(shí)任務(wù)(實(shí)例代碼)
這篇文章主要介紹了springboot實(shí)現(xiàn)多實(shí)例crontab搶占定時(shí)任務(wù),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-01-01
通過(guò)Java實(shí)現(xiàn)zip文件與rar文件解壓縮的詳細(xì)步驟
這篇文章主要給大家介紹了如何通過(guò)?Java?來(lái)完成?zip?文件與?rar?文件的解壓縮,文中通過(guò)代碼示例講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-07-07
java Swing JFrame框架類(lèi)中setDefaultCloseOperation的參數(shù)含義與用法示例
這篇文章主要介紹了java Swing JFrame框架類(lèi)中setDefaultCloseOperation的參數(shù)含義與用法,結(jié)合實(shí)例形式分析了Swing組件的JFrame框架類(lèi)中setDefaultCloseOperation方法的簡(jiǎn)單使用技巧,需要的朋友可以參考下2017-11-11
MyBatis動(dòng)態(tài)SQL中的trim標(biāo)簽的使用方法
這篇文章主要介紹了MyBatis動(dòng)態(tài)SQL中的trim標(biāo)簽的使用方法,需要的朋友可以參考下2017-05-05
springmvc實(shí)現(xiàn)導(dǎo)出數(shù)據(jù)信息為excle表格示例代碼
本篇文章主要介紹了springmvc實(shí)現(xiàn)導(dǎo)出數(shù)據(jù)信息為excle表格,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧。2017-01-01

