亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

JDK8中String的intern()方法實例詳細解讀

 更新時間:2022年09月22日 10:53:50   作者:小王寫博客  
String字符串在我們?nèi)粘i_發(fā)中最常用的,當(dāng)然還有他的兩個兄弟StringBuilder和StringBuilder,接下來通過本文給大家介紹JDK8中String的intern()方法詳細解讀,需要的朋友可以參考下

一、前言

String字符串在我們?nèi)粘i_發(fā)中最常用的,當(dāng)然還有他的兩個兄弟StringBuilder和StringBuilder。他三個的區(qū)別也是面試中經(jīng)常問到的,大家如果不知道,就要先去看看了哈!最近也是看周志明老師的深入JVM一書中寫到關(guān)于intern()方法的介紹,小編也是以前沒在開發(fā)中用到。但是面試題還是很多的,所以特意研究了一天,寫下來記錄一下自己的收獲,希望也可以幫助到大家??!

二、圖文理解String創(chuàng)建對象

1.例子一

String str1 = "wang";

JVM在編譯階段會判斷字符串常量池中是否有 "wang" 這個常量對象如果有,str1直接指向這個常量的引用,如果沒有會在常量池里創(chuàng)建這個常量對象。

2.例子二

String str2 = "學(xué)" + "Java";

JVM編譯階段過編譯器優(yōu)化后會把字符串常量直接合并成"學(xué)Java",所有創(chuàng)建對象時只會在常量池中創(chuàng)建1個對象。

3.例子三

String str3 = new String("學(xué)Java");

當(dāng)代碼執(zhí)行到括號中的"學(xué)Java"的時候會檢測常量池中是否存在"學(xué)Java"這個對象,如果不存在則在字符串常量池中創(chuàng)建一個對象。當(dāng)整行代碼執(zhí)行完畢時會因為new關(guān)鍵字在堆中創(chuàng)建一個"學(xué)Java"對象,并把棧中的變量"str3"指向堆中的對象,如下圖所示。這也是為什么說通過new關(guān)鍵字在大部分情況下會創(chuàng)建出兩個字符串對象!

4.例子四

String str4 = "學(xué)Java";
String str5 = "學(xué)Java";
System.out.println(str4 == str5); // 如下圖得知為:true

第一行代碼:
JVM在編譯階段會判斷字符串常量池中是否有 "學(xué)Java" 這個常量對象如果有,str4直接指向這個常量的引用,如果沒有會在常量池里創(chuàng)建這個常量對象。
第二行代碼:
再創(chuàng)建"學(xué)Java",發(fā)現(xiàn)字符串常量池中存在了"學(xué)Java",所以直接將棧中的str5變量也指向字符串常量池中已存在的"學(xué)Java"對象,從而避免重復(fù)創(chuàng)建對象,這也是字符串常量池存在的原因。

5.例子五

String str6 = new String("學(xué)") + new String("Java");

首先,會先判斷字符串常量池中是否存在"學(xué)"字符串對象,如果不存在則在字符串常量池中創(chuàng)建一個對象。當(dāng)執(zhí)行到new關(guān)鍵字在堆中創(chuàng)建一個"學(xué)"字符串對象。后面的new String("Java"),也是這樣。
然后,當(dāng)右邊完成時,會在堆中創(chuàng)建一個"學(xué)Java"字符串對象。并把棧中的變量"str6"指向堆中的對象。
總結(jié):一句代碼創(chuàng)建了5個對象,但是有兩個在堆中是沒有引用的,按照垃圾回收的可達性分析,他們是垃圾就是"學(xué)"、"Java"這倆垃圾

心得:
上面代碼進行反編譯:

String str6 = (new StringBuilder()).append(new String("\u5B66"))
					.append(new String("Java")).toString();

底層是一個StringBuilder在進行把兩個對象拼接在一起,最后棧中str6指向堆中的"學(xué)Java",其實是StringBuilder對象。

6.例子六

String str7 = new String("學(xué)Java");
String str8 = new String("學(xué)Java");
System.out.println(str7 == str8); // 如下圖得知為:false

執(zhí)行到第一行:
執(zhí)行到括號內(nèi)的"學(xué)Java",會先判斷字符串常量池中是否存在"學(xué)Java"字符串對象,如果沒有則在字符串常量池中創(chuàng)建一個"學(xué)Java"字符串對象,執(zhí)行到new關(guān)鍵字時,在堆中創(chuàng)建一個"學(xué)Java"字符串對象,棧中的變量str7的引用指向堆中的"學(xué)Java"字符串對象。
執(zhí)行到第二行:
當(dāng)執(zhí)行到第二行括號中的"學(xué)Java"時,先判斷常量池中是否有"學(xué)Java"字符串對象,因為第一行代碼已經(jīng)將其創(chuàng)建,所以有的話就不創(chuàng)建了;執(zhí)行到new關(guān)鍵字時,在堆中創(chuàng)建一個"學(xué)Java"字符串對象,棧中的變量str8的引用指向堆中的"學(xué)Java"字符串對象。

三、深入理解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&trade; 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();
}

翻譯過來就是,當(dāng)intern()方法被調(diào)用的時候,如果字符串常量池中已經(jīng)存在這個字符串對象了,就返回常量池中該字符串對象的地址;如果字符串常量池中不存在,就在常量池中創(chuàng)建一個指向該對象堆中實例的引用,并返回這個引用地址。

2. 例子一

我們直接先把周志明老師的在深入JVM一書的例子:

String str1 = new StringBuilder("計算機").append("軟件").toString(); 
System.out.println(str1.intern() == str1);
String str2 = new StringBuilder("ja").append("va").toString(); 
System.out.println(str2.intern() == str2);

這段代碼在JDK 6中運行,會得到兩個false,而在JDK 7、8中運行,會得到一個true和一個false。產(chǎn) 生差異的原因是,在JDK 6中,intern()方法會把首次遇到的字符串實例復(fù)制到永久代的字符串常量池 中存儲,返回的也是永久代里面這個字符串實例的引用,而由StringBuilder創(chuàng)建的字符串對象實例在 Java堆上,所以必然不可能是同一個引用,結(jié)果將返回false。 而JDK 7(以及部分其他虛擬機,例如JRockit)的intern()方法實現(xiàn)就不需要再拷貝字符串的實例到永久代了,既然字符串常量池已經(jīng)移到Java堆中,那只需要在常量池里記錄一下首次出現(xiàn)的實例引用即可,因此intern()返回的引用和由StringBuilder創(chuàng)建的那個字符串實例就是同一個。而對str2比較返 回false,這是因為“java”(下面解釋)這個字符串在執(zhí)行String-Builder.toString()之前就已經(jīng)出現(xiàn)過了,字符串常量 池中已經(jīng)有它的引用,不符合intern()方法要求“首次遇到”的原則,“計算機軟件”這個字符串則是首次出現(xiàn)的,因此結(jié)果返回true。

java為什么已經(jīng)存在了?

1.我們在一個類中輸入System,然后點擊到這個方法中,方法內(nèi)容如下:

public final class System {
	// ...
	private static void initializeSystemClass() {
		// ...
		sun.misc.Version.init();
		// ...
	}
	// ...
}

2.我們點擊上面的Version類,類內(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";

我們開始例子和詳細解釋,發(fā)車了,大家坐好哦!
以下例子來自:原博客,解釋是為小編自己的理解。

3. 例子二

String str1 = new String("wang");
str1.intern();
String str2 = "wang";
System.out.println(str1 == str2); // false

執(zhí)行第一行代碼:
首先執(zhí)行到"wang",因為字符串常量池中沒有,則會在字符串常量池中創(chuàng)建"wang"字符串對象。
然后執(zhí)行到new關(guān)鍵字時,在堆中創(chuàng)建一個"wang"的對象,并把棧中的str1的引用指向"wang"對象。

執(zhí)行第二行代碼:
這里我們看到就是str1手動把"wang"放在字符串常量池中,但是發(fā)現(xiàn)字符串常量池中已經(jīng)存在"wang"字符串對象,所以直接把已存在的引用返回。雖然str1.intern()指向了字符串常量池中的"wang",但是我們第四行代碼并沒有拿str1.intern()作比較,所以還是false。

執(zhí)行第三行代碼:
首先通過第一行代碼,字符串常量池中已經(jīng)有"wang"字符串對象了,所以本行代碼只需要把棧中的str2變量指向字符串常量池中的"wang"即可。

執(zhí)行第四行代碼:
如上和下圖可見,我們的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"時,因為字符串常量池中沒有,則會在字符串常量池中創(chuàng)建一個"wang"字符串對象;
然后執(zhí)行到"zhen"時,因為字符串常量池中沒有,則會在字符串常量池中創(chuàng)建一個"zhen"字符串對象;
最后執(zhí)行到new關(guān)鍵字時,看到是兩個,但是底層字節(jié)碼文件反編譯的是使用如下可見,只會有一個StringBuilder對象生成,于是將棧中的str3的引用指向堆中的"wangzhen"對象。

String str3 = (new StringBuilder()).append(new String("wang"))
					.append(new String("zhen")).toString();

執(zhí)行到第二行代碼:
這里我們看到就是str3手動把"wangzhen"放在字符串常量池中,在字符串常量池中沒有找到"wangzhen",于是把str3 .intern()引用指向堆中的"wangzhen"的地址?,F(xiàn)在str3和str3 .intern()一樣

執(zhí)行到第三行代碼:
判斷字符串常量池中是否存在"wangzhen"字符串對象,第二行代碼已經(jīng)在字符串常量池中創(chuàng)建了"wangzhen",不過str4是指向str3中堆的引用(看圖就明白了)。

執(zhí)行到第四行代碼:
str3和str3 .intern()引用一樣,str3 .intern()和str4是一個,所以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"時,因為字符串常量池中沒有,則會在字符串常量池中創(chuàng)建一個"wang"字符串對象;
然后執(zhí)行到"zhen"時,因為字符串常量池中沒有,則會在字符串常量池中創(chuàng)建一個"zhen"字符串對象;
最后執(zhí)行到new關(guān)鍵字時,看到是兩個,但是底層字節(jié)碼文件反編譯的是使用如下可見,只會有一個StringBuilder對象生成,于是將棧中的str5的引用指向堆中的"wangzhen"對象。同上面的反編譯代碼

執(zhí)行到第二行代碼:
執(zhí)行到"wangzhen",判斷字符串常量池中是否存在"wangzhen",發(fā)現(xiàn)沒有,在字符串常量池中創(chuàng)建"wangzhen"字符串對象,然后把棧中的str6變量的引用指向"wangzhen"對象。

執(zhí)行到第三行代碼:
這里我們看到就是str5手動把"wangzhen"放在字符串常量池中,我們發(fā)現(xiàn),在字符串常量池中已存在"wangzhen",于是str5 .intern()就是"wangzhen"對象的地址。我們還沒沒有收到返回值

如下圖,我們看到肯定返回false,此時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"對象,發(fā)現(xiàn)沒有,我們在字符串常量池中創(chuàng)建"wangzhen"字符串對象;

執(zhí)行到第三行代碼:
執(zhí)行到str7.intern()這里,我們看到就是str7手動把"wangzhen"放在字符串常量池中,在字符串常量池中已結(jié)存在"wangzhen",于是把字符串常量池"wangzhen"的地址?,F(xiàn)在str7和str7 .intern()一樣

執(zhí)行到第四行代碼:
str7的指向為堆中的"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手動把"wangzhen"放在字符串常量池中,在字符串常量池中沒有"wangzhen",于是把str3 .intern()引用指向堆中的"wangzhen"的地址。現(xiàn)在str9和str9.intern()一樣

執(zhí)行到第三行代碼:
str9指向堆內(nèi)存中的"wangzhen",執(zhí)行到"wangzhen"時,發(fā)現(xiàn)字符串常量池中已存在,直接返回str9指向的引用即可,故返回true。

四、總結(jié)

經(jīng)過這么多例子,大家應(yīng)該明白了吧,還是要自己跟著例子進行換一下jvm內(nèi)存圖,這樣就理解記憶,也不會輕易忘記!

到此這篇關(guān)于JDK8中String的intern()方法詳細解讀的文章就介紹到這了,更多相關(guān)JDK8中String的intern()內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論