學習背景
進入正文學習字串的intern()方法之前,先給下這4個問題,看下自己是否都知道答案?
1、String s1 = “a” + “b”; //創建了幾個物件?
2、String s2 = new String(“ab”); //創建了幾個物件?
3、String s3 = new String(“a”) + new String(“b”); //創建了幾個物件?
4、String s4= new String(“a”) + new String(“a”); s4.intern(); //創建了幾個物件?
如果都清楚,恭喜你,大佬一枚,不用往下學習了,哈哈哈!
那如果不太確定或者需要加深自己的理解,建議進入正文一起來了解下吧!
當然,也可以拉到最后有答案!
String#intern()示例代碼
先來執行一下String呼叫intern()方法的一段示例代碼:
public class StringInternTest {
public static void main(String[] args) {
String reference1 = new String("a");
reference1.intern();
String reference2 = "a";
System.out.println(reference1 == reference2);
String reference3 = new String("a") + new String("a");
reference3.intern();
String reference4 = "aa";
System.out.println(reference3 == reference4);
}
}
JDK1.6 執行輸出結果:
false
false
JDK1.7 執行輸出結果:
false
true
大家可以先思考一下為什么結果是這樣的?往下會具體介紹!
String##intern()原始碼
先來看一下intern()方法的JDK原始碼如下:
/**
* 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();
很顯然通過原始碼可以看到intern()是一個native本地方法,但是native具體實作原始碼已經被隱藏了,這是一個歷史故事了,SUN公司在JDK7開發期間,由于技術競爭和商業競爭陷入泥潭,無力再投入精力繼續研發JDK,Oracle半路殺出直接收購Sun公司,Oracle接管JDK的研發后,發版了自己的Oracle JDK,Oracle的native底層等很多原始碼就被隱藏了,不過Oracle官方也宣告OpenJDK和Oracle JDK7及以后版本,原始碼幾乎是一模一樣的,想要了解native底層原始碼具體實作程序,可以下載開源的OpenJDK的原始碼進行查看,
OpenJDK官網:https://hg.openjdk.java.net/
GitHub也開源啦:https://github.com/openjdk/jdk
例如String對應的OpenJDK底層原始碼主入口:jdk7\jdk\src\share\native\java\lang\String.c
Java_java_lang_String_intern(JNIEnv *env, jobject this)
{
return JVM_InternString(env, this);
}
native底層方法的實作,需要掌握C和C++的語法,學習門檻要求比較高,這里不是我們要學習的重點,不做具體介紹,
String#intern()方法作用
前面JDK原始碼intern()方法的英文注釋已經說明了intern()方法的有具體用途了,網上也有很多說明,不過這里我以個人的理解以及話術簡單概括下intern()方法的作用如下:
(1)只要呼叫String物件的intern(),都會去找到字串常量池,然后判斷String物件的字串內容是否已經存在常量池中,
不存在,則往字串常量池中創建該字串內容的物件(JDK6及之前)或創建新的參考并指向堆區已有物件地址(JDK7之后),存在則直接回傳,(2)JDK7時,字串常量池從永久代脫離,遷移到堆區中,相比于JDK6,變化不只是字串常量池遷移到堆區而已,另一個變化就是呼叫字串物件的intern()方法,如果字串常量池中不存在該字串內容的物件,則不會再像JDK6直接往字串常量池中創建該字串內容的物件,而是創建一個新的參考并指向堆區已有物件地址,實作字串常量池和堆區字串共用的目的,效率更高,
JDK6 String#intern()執行說明
一張圖介紹前面示例代碼JDK6執行程序如下:

/**
* JDK6 String#intern()執行說明
*/
public class StringInternTest {
public static void main(String[] args) {
//Step6.1
//創建了2個物件,分別是堆區的String物件和字串常量池中的"a"物件,reference1參考指向在堆區中的物件地址
String reference1 = new String("a");
//Step6.2
//判斷字串常量池,是否該字串"a",此前,池中已經有該物件了,因此會回傳池中的物件地址的參考
reference1.intern();
//Step6.3
//字串常量池中已存在字串"a",因此reference2參考直接指向物件在字串常量池中的地址
String reference2 = "a";
//reference1指向物件地址是在堆區,reference2指向物件地址是在永久代的常量池,顯然不可能一樣
System.out.println(reference1 == reference2);
//Step6.4
//創建了2個物件,分別是在堆區的String物件(內容是"aa")和字串常量池中的"a"物件
//reference3參考指向物件在堆區中的地址,這程序還會在堆區創建了兩個無參考的"a"物件,這里不做討論
String reference3 = new String("a") + new String("a");
//Step6.5
//判斷永久代中的字串常量池,是否存在該字串"aa",這里是首次出現,因此直接將字串拷貝并放到池中
reference3.intern();
//Step6.6
//池中已存在該字串,reference2參考直接指向物件在永久代字串常量池中的地址
String reference4 = "aa";
//同樣,reference3指向堆區地址,reference4指向永久代常量池中的地址,顯然不可能一樣
System.out.println(reference3 == reference4);
}
}
JDK7 String#intern()執行說明
一張圖介紹前面示例代碼JDK7執行程序如下:

/**
* JDK1.7 String#intern()執行說明
**/
public class StringInternTest {
public static void main(String[] args) {
//Step7.1
//創建了2個物件,分別是堆區的String物件和字串常量池中的"a"物件,reference1參考指向在堆區中的物件地址
String reference1 = new String("a");
//Step7.2
//判斷字串常量池,是否該字串"a",此前,池中已經有該物件了,因此會回傳池中的物件地址的參考
reference1.intern();
//Step7.3
//字串常量池中已存在字串"a",因此reference2參考直接指向物件在字串常量池中的地址
String reference2 = "a";
//reference1指向物件地址是在堆區,reference2指向物件地址是在堆區的字串常量池,參考指向的物件地址不一樣
System.out.println( reference1 == reference2);
//Step7.4
//創建了2個物件,分別是在堆區的String物件(內容是"aa")和字串常量池中的"a"物件(注意并不會創建"aa"物件)
//reference3參考指向物件在堆區中的地址,這程序還會在堆區創建了兩個無參考的"a"物件,這里不做討論
String reference3 = new String("a") + new String("a");
//Step7.5
//判斷堆區的字串常量池中,是否存在該字串"aa",顯然這里是首次出現
//但并不像JDK6會新建物件"aa"存盤,而是存盤指向堆區已有物件地址的一個新參考
reference3.intern();
//Step7.6
//指向池中已有該字串的新參考,reference4參考直接指向字串常量池中的這個新參考,新參考則指向堆區已有物件地址
String reference4 = "aa";
//reference4指向新參考,而新參考則指向堆區已有物件地址,跟reference3參考直接指向的物件地址是同一個
System.out.println(reference3 == reference4);
}
經典面試問題之創建了幾個物件?
在實際的Java面試當中,經常會被問到字串創建了幾個物件的問題,主要是考察學習者對于物件的實體化以及字串常量池在JVM結構體系中是如何運行的,個人覺得比較常見問題,無法就是如下幾個:
1、最簡單的比如:String s1 = “a” + “b”;創建了幾個物件?
答:最多1個,多個字串常量相加會被編譯器優化為一個字串常量即"ab",如果字串常量池不存在,則創建該物件,
2、相對簡單的比如:String s1 = new String(“ab”);創建了幾個物件?
答:1個或2個,使用new實體化物件,必然會在堆區創建一個物件,另外一個就是如果在字串常量池中不存在"ab"這個物件,則會創建這個"ab"常量物件,
3、稍微難一點的比如:String s2 = new String(“a”) + new String(“b”);創建了幾個物件?
答:至少4個,最多6個
1個new StringBuilder()和2個new String()
另外"a"、"b"可能會在常量池新建物件
最后1個是,StringBuilder()的toString()方法底層實作是new String(value, 0, count)
有的同學可能會有疑問,那"ab"字串不會在常量池中也創建嗎?
答案是,不會,最后StringBuilder的toString() 的呼叫,并不會在字串常量池中去創建"ab"物件,
兩個new String相加會被優化為StringBuilder,可以通過javac和javap查看匯編指令如下:
javac InternTest.java
javap -c InternTest
public class com.justin.java.lang.InternTest {
public com.justin.java.lang.InternTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
7: new #4 // class java/lang/String
10: dup
11: ldc #5 // String a
13: invokespecial #6 // Method java/lang/String."<init>":(Ljava/lang/String;)V
16: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: new #4 // class java/lang/String
22: dup
23: ldc #8 // String b
25: invokespecial #6 // Method java/lang/String."<init>":(Ljava/lang/String;)V
28: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
31: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
34: astore_1
35: return
}
6、最難的無非就是再呼叫intern()方法,比如:
String s3= new String(“a”) + new String(“a”);
s3.intern();創建了幾個物件?
答:最少4個,最多7個
1個new StringBuilder()和兩個new String
另外"a"、"b"可能會在常量池新建物件
最后1個是,StringBuilder()的toString()方法底層實作是new String(value, 0, count)
最后是呼叫intern()方法,會去找到字串常量池,判斷"ab"是否存在,不存在,則創建"ab"物件,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/305669.html
標籤:java
