首先來看JDK1.6
顯然JDK1.6及其以前版本常量池是放在 Perm 區(屬於方法區)中的,熟悉JVM的話應該知道這是和堆區完全分開的。
1.6中intern方法的作用:
比如String s = new String("SEU_Calvin"),再調用s.intern(),此時返回值還是字符串"SEU_Calvin",表面上看起來好像這個方法沒什么用處。但實際上,在JDK1.6中它做了個小動作:檢查字符串池里是否存在"SEU_Calvin"這么一個字符串,如果存在,就返回池里的字符串;如果不存在,該方法會把"SEU_Calvin"添加到字符串池中,然后再返回它的引用。
String s = new String("1"); s.intern(); String s2 = "1"; System.out.println(s == s2); String s3 = new String("1") + new String("1"); s3.intern(); String s4 = "11"; System.out.println(s3 == s4);
那么來看這段代碼
使用引號聲明的字符串都是會直接在字符串常量池中生成的,而 new 出來的 String 對象是放在堆空間中的。所以兩者的內存地址肯定是不相同的,即使調用了intern()方法也是不影響的。
所以兩個都是false
然后JDK1.7及其以后版本
還是剛才的代碼
String s = new String("1"); s.intern(); String s2 = "1"; System.out.println(s == s2); String s3 = new String("1") + new String("1"); s3.intern(); String s4 = "11"; System.out.println(s3 == s4);
結果是在JDK1.7以及以后版本中答案卻是false true
第一到四行代碼運行過程:
第一行:new String(“1”) 中的“1”,那么首先檢查常量池是否有“1”,如果沒有則在堆上創建“1”,並將堆上“1”的引用添加到常量池,如果有,則堆上新建“1”存儲常量池“1”的引用
然后new String 則在堆上創建另一個不同於第一個在堆上“1”的“1”,並將棧上的s指向該1
第二行:檢查常量池中是否有“1”,如果沒有就將s指向堆中的“1”的引用存入常量池,如果有返回常量池“1”的引用
String s2=“1”引用賦值,則直接檢查常量池中是否有1,如果有則s2指向常量池中的1(即堆中的第一個“1”) 若沒有則在堆上創建“1”,並將常量池存入該“1”的引用
顯然s2指向的是之前new String(“1”)中的1(堆中的第一個“1”)
第四步 s指向的是堆中第二個“1” s2指向堆中第一個“1” 固為false
第四行到第8行代碼分析:
第一行:堆上先建一個“1” ,其引用至常量池 s3指向堆中“11” 此時常量池只有“1”,無“11”
s3.intern()將s3的“11”引用加入到常量池中
s4=“11”檢查常量池是否有11 如果有(常量池中的“11”)為s3的引用
則將s4指向常量池中的“11”(就是s3) 所以s3=s4
但需要注意以下
String s1 = new String("1") + new String("1") (變量+...)和String s2 = "1"+"1"(常量加常量)不同
s1在賦值階段 調用StringBuilder在堆上創建對象。 而s2是檢查常量池,從而決定在堆上創建還是直接引用常量池中的變量
final修飾的串,final修飾的串在編譯時就會被替換成常量(即 final s1就看成“11”常量)
圖文部分來自:
https://blog.csdn.net/seu_calvin/article/details/52291082