聊一聊讓我矇蔽一夜的各類常量池

在寫以前咱們先來看幾個問題,假如你對這些問題已經很懂了的話,那大可不用看這篇文章,若是不大懂的話,那麼能夠看看個人想法。java

問題1:面試

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

答案輸出:app

JDK1.6是 false falseui

JDK1.7是 false true;code

問題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

問題到此就結束了,以上即是本身的理解,以上若是有不對勁的地方,很是歡迎你的指點。

關注公衆號「苦逼的碼農」,獲取更多原創文章,後臺回覆「禮包」送你一份特別的大禮包
相關文章
相關標籤/搜索