聊一聊讓我蒙蔽一晚上的各種常量池


在寫之前我們先來看幾個問題,假如你對這些問題已經很懂了的話,那大可不用看這篇文章,如果不大懂的話,那么可以看看我的想法。
問題1:

public static void main(String[] args){
    String t1 = new String("1");
    t1.intern();
    String t2 = "1";
    System.out.println(t1 == t2);

    String t3 = new String("2") + new String("2");
    t3.intern();
    String t4 = "22";
    System.out.println(t3 == t4);
}

答案輸出:
JDK1.6是 false false
JDK1.7是 false true;
問題2(把問題1的語句調換一下位置)

public static void main(String[] args){
    String t1 = new String("2");
    String t2 = "2";
    t1.intern();
    System.out.println(t1 == t2);

    String t3 = new String("2") + new String("2");
    String t4 = "22";
    t3.intern();
    System.out.println(t3 == t4);
}

答案輸出:
false false
對於這兩個問題,看了幾個人的博客,可謂百花齊放,越看越懵逼
問題3

public static void main(String[] args){
    Integer a = 1;
    Integer b = 2;
    Integer c = 3;
    Integer d = 3;
    Integer e = 321;
    Integer f = 321;
    Long g = 3L;

    System.out.println(c == d);
    System.out.Println(e == f);
    System.out.println(c == (a + b));
    System.out.println(c.equals(a+b));
    System.out.println(g == (a + b));
    System.out.println(g.equals(a + b));
}

答案輸出:
true
false
true
true
true
false
問題4:

運行時常量池與字符串常量池是什么關系?包含?

在解決問題之前,我們先來簡單了解一些常量池的一些知識點(大部分來源於周志明的深入Java虛擬機這本書)。

JVM中的幾種常量池

1.class文件常量池

在Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池(Constant Pool Table),用於存放編譯期生成的各種字面量和符號引用。

這里簡單解釋下字面量和符號引用
字面量
字面量類似與我們平常說的常量,主要包括:

  1. 文本字符串:就是我們在代碼中能夠看到的字符串,例如String a = “aa”。其中”aa”就是字面量。
  2. 被final修飾的變量。

符號引用
主要包括以下常量:

  1. 類和接口和全限定名:例如對於String這個類,它的全限定名就是java/lang/String。
  2. 字段的名稱和描述符:所謂字段就是類或者接口中聲明的變量,包括類級別變量(static)和實例級的變量。
  3. 方法的名稱和描述符。所謂描述符就相當於方法的參數類型+返回值類型

2.運行時常量池

我們上面說的class文件中的常量池,它會在類加載后進入方法區中的運行時常量池。並且需要的注意的是,運行時常量池是全局共享的,多個類共用一個運行時常量池。並且class文件中常量池多個相同的字符串在運行時常量池只會存在一份。
注意運行時常量池存在於方法區中。

3.字符串常量池

看名字我們就可以知道字符串常量池會用來存放字符串,也就是說常量池中的文本字符串會在類加載時進入字符串常量池。
那字符串常量池和運行時常量池是什么關系呢?上面我們說常量池中的字面量會在類加載后進入運行時常量池,其中字面量中有包括文本字符串,顯然從這段文字我們可以知道字符串常量池存在於運行時常量池中。也就存在於方法區中。
不過在周志明那本深入java虛擬機中有說到,到了JDK1.7時,字符串常量池就被移出了方法區,轉移到了里了。
那么我們可以推斷,到了JDK1.7以及之后的版本中,運行時常量池並沒有包含字符串常量池,運行時常量池存在於方法區中,而字符串常量池存在於中。

說了這么多,現在我們開始來解決上面提出了問題。

解決問題

問題1:

public static void main(String[] args){
    String t1 = new String("1");
    t1.intern();
    String t2 = "1";
    System.out.println(t1 == t2);

    String t3 = new String("2") + new String("2");
    t3.intern();
    String t4 = "22";
    System.out.println(t3 == t4);
}

答案輸出:
JDK1.6是 false false。
JDK1.7是 false true;
在解決這個問題之前,我們先來看另外一道面試中經常會問到的問題。

String t = new String("tt");

假如程序中只有這樣一行代碼,那么這行代碼創建了幾個對象?
我們上面說過,”tt”屬於字面量,那么它會在類加載之后存在於字符串常量池中,也就是說,在 String t = new String(“tt”)這句代碼執行之前,字符串常量池就已經創建了”tt”這個字符串對象了,我們都知道,new這個關鍵字會在堆中創建一個對象。
所以,這段代碼創建了兩個對象。一個在堆中,一個在字符串常量池中。
那么下面這段代碼又是創建了幾個對象呢?

String t1 = new String("tt");
String t2 = new String("tt");

答是這段代碼創建了三個對象,我們上面說了,字符串常量池只會保存一份內容相同的字符串。也就是說,在這兩句代碼執行之前,字符串常量池就已經創建了內容為”tt”的對象了。這兩句代碼執行之后,又在堆中創建了兩個,所以一共創建了三個。
那么下面這段代碼又是創建了幾個對象?

String t = "tt";

答是1個,在這段代碼執行之前,字符串常量池已經創建了一個”tt”的對象,但由於這行代碼並非用new的方法,所以虛擬機會在字符串常量池中尋找是否有內容為”tt”的字符串對象,如果有,則直接返回這個字符串的引用,所以最終結果只創建了一個對象。
回到我們的問題,在這里我們先解釋下String 的intern方法


例如我們調用了t.intern()。
在JDK1.6的時候,調用了這個方法之后,虛擬機會在字符串常量池在查找是否有內容與”tt”相等的對象,如果有,則返回這個對象,如果沒有,則會在字符串常量池中添加這個對象。注意,是把這個對象添加到字符串常量池。
到了JDK1.7之后,如果調用了intern這個方法,虛擬機會在字符串常量池在查找是否有內容與”tt”相等的對象,如果有,則返回這個對象,如果沒有。則會在堆中把這個對象的引用復制添加到字符串常量池中。注意,這個時候添加的是對象在堆中的引用。


現在開始來分析問題中的代碼
t1 = new String(“1”)。
這句代碼執行之前,字符串常量池中已經有”t”這個對象,執行之后會在堆中也創建一個”t”的對象,此時t1指向的是堆中的對象
t1.intern();
這句代碼執行之后,會在字符串常量池尋早內容為”t”的對象,字符串常量池已經存在這個對象了,把這個對象返回(不過返回之后並沒有變量來接收)。
t2 = “1”。
這句執行后會在字符串常量池查找內容為”t”的對象,字符串常量池已經有這個對象了,返回給t2,此時t2指向的是常量池中的對象
一個是常量池中的對象,一個是在堆中的對象,兩者能相等嗎?因此
t1 與 t2不相等。


接着下面
t3 = new String(“2”) + new String(“2”);
這段代碼調用之前,字符串常量池有一個”2”的對象,執行之后,實際上會調用StringBuilder的append()方法類進行拼接,最后在堆中創建一個”22”的對象,注意,此時字面量並沒有”22”這個字符串,也就是說在字符串常量池並沒有”22”這個對象。此時t3指向堆中”22”這個對象
t3.intern();
執行這個方法之后


在JDK1.6的時候,它在字符串常量池中並沒有找到內容為”22”的對象,所以這個時候會把這個對象添加到字符串常量池,並把這個對象返回(此時並沒有變量來接收這個返回的對象)。注意添加的是對象,而並非引用。
t4 = “22”。
這句代碼執行后,會返回字符串常量池中內容為”22”對象,此時t4指向的是字符串常量池中的對象
顯然,一個對象在字符串常量池,一個在堆中,兩個對象並非是同一個對象,因此在JDK1.6的時候,t3與t4不相等。


但是在JDK1.7的時候
t3.intern()執行之后,由於在字符串常量池在並沒有內容為”22”的對象,所以會把堆中該對象的引用賦值到字符串常量池。注意此時字符串常量池保存的是堆中這個對象的引用
t4 = “22”。
執行這句代碼之后,從字符串常量池返回給t4的是堆中對象的引用。此時t4指向的實際上是堆中對象的引用,也就是說,t3和t4指向的是同一個對象。
因此t3與t4相等。
不知道你明白了沒有?反正我是搞了好久才明白…

問題2

至於問題2,我就只講下半部分的代碼,上半部分如果你看懂了問題1,那么問題2也差不多自然懂了。

String t3 = new String("2") + new String("2");
String t4 = "22";
t3.intern();
System.out.println(t3 == t4);

t3 = new String(“2”) + new String(“2”)。
這段代碼調用之前,字符串常量池有一個”2”的對象,執行之后,實際上會調用StringBuilder的append()方法類進行拼接,最后在堆中創建一個”22”的對象。此時t3指向堆中”22”這個對象
t4 = “22”。
這句代碼執行之前,字符串常量池已經存在”22”這個對象了,所有直接把這個對象返回給t4,此時t4指向的是字符串常量池中的對象.
所以t3和t4肯定不是同一個對象啊,t3.intern這句幾乎可以忽略,不會給t3和t4造成任何影響。

問題3

public static void main(String[] args){
    Integer a = 1;
    Integer b = 2;
    Integer c = 3;
    Integer d = 3;
    Integer e = 321;
    Integer f = 321;
    Long g = 3L;

    System.out.println(c == d);
    System.out.Println(e == f);
    System.out.println(c == (a + b));
    System.out.println(c.equals(a+b));
    System.out.println(g == (a + b));
    System.out.println(g.equals(a + b));
}

對於這個問題,我簡單說一下可能你就懂了。

(1). 內存中有一個java基本類型封裝類的常量池。這些類包括
Byte, Short, Integer, Long, Character, Boolean。需要注意的是,Float和Double這兩個類並沒有對應的常量池。


(2).上面5種整型的包裝類也只是在對象數值在-128~127才可以使用這些常量池。


(3). 在周志明的那本虛擬機中有這樣一句話:包裝類的
“==”運行符在不遇到算術運算的情況下不會自動拆箱,以及他們的equals()方法不處理數據類型的關系,可以推斷出如果遇到“==”兩邊有算術運算是話就會自動拆箱和進行數據類型轉換處理。


(4).Long的equals方法會先判斷是否是Long類型。


(5).無論是Integer還是Long,他們的equals方法比較的是數值。


所以:
System.out.println(c == d)。
由於常量池的作用,c與d指向的是同一個對象(注意此時的==比較的是對象,也就是地址,而不是數值)。因此為true
System.out.println(e == f)。
由於321超過了127,因此常量池失去了作用,所以e和f數值雖然相同,但不是同一個對象,以此為false。
System.out.println(c == (a+b))。
此時==兩邊有算術運算,會進行拆箱,因此此時比較的是數值,而並非對象。因此為true。
System.out.println(c.equals(a+b))
c與a+b的數值相等,為true。
System.out.pirnln(g == (a + b))
由於==兩邊有算術運算,所以比較的是數值,因此為true。
System.out.println(g.equals(a+b))。
Long類型的equal在比較是時候,會先判斷a+b是否為Long類型,顯然a+b不是,因此false

問題到此就結束了,以上便是自己的理解,以上如果有不對勁的地方,非常歡迎你的指點。

如果覺得文章不錯的話,不妨點個贊再走。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM