工作10年後,再看String s = new String(“xyz”) 創建了幾個對象?
這個問題相信每個學習java的同學都不陌生,作為一個經典的面試題,到現在工作這麼多年了我真是認為挺操蛋的一個問題,在網上到現在你仍然可以看見很多討論這個問題的人,其中不乏工作很多年的人都有爭論,我認為還是有必要來說一說這個問題的。
從方法區說起
常量池存在於方法區,而方法區在jdk1.7版本前後改變比較大,所以還是先來說說方法區的演變。
在jdk1.7版本之前,常量池存在於方法區,方法區是堆的一個邏輯部分,他有一個名字叫做非堆。
1.7版本把字元串常量池放到了堆中。
而在1.8以後,則是移除了永久代,方法區概念保留,方法區的實現改為了元空間,常量池還是在堆中。
為什麼要說方法區的改變,只是為了文章接下來的內容不會由於JDK的版本而產生分歧,接下來內容都會以jdk1.8版本作為基礎來討論。
String s = new String(“xyz”);
先來一段程式碼
public class Test { public static void main(String[] args) { String s = "xyz"; } }
接著我們javac編譯程式碼,然後用javap來反編譯,執行javap -c Test
從結果來看,ldc命令在常量池中創建了一個”xyz”的對象,然後把他推至操作數棧頂,然後astore保存到局部變數,return返回。
接著看第二段面試題中的程式碼
public class Test { public static void main(String[] args) { String s = new String("xyz"); } }
同樣反編譯分析
很明顯,我們看到new 創建了一個String對象,同時ldc在常量池中創建了”xyz”字元串對象,之後invokespecial執行構造函數,astore_1賦值,return返回。
通過以上兩個例子,可以知道String s = new String(“xyz”); 創建了2個對象,而有些答案說的3個對象,則是把引用s也算作一個對象。
還有答案說xyz存在就創建了2個,不存在就創建了3個(包含引用s),再來測試一下。
public class Test { public static void main(String[] args) { String s = "xyz"; String s2 = new String("xyz"); } }
從這裡,很明顯的發現這就是我們例子1和2的一個結合,但是注意兩次ldc後面的#2,#號代表著索引,說明第二次new String(“xyz”)的時候並沒有重新創建xyz對象。
一些常見的指令助記符含義:
- nop, 什麼都不做。
- aconst_null,將 null 推送至棧頂。
- iconst_i(變數數字),將 int 型 i 推送至棧頂。同理有lconst_0,fconst_0這種你應該知道什麼意思了
- ldc,將 int,float 或 String 型常量值從常量池中推送至棧頂。
- iload,將指定的 int 型局部變數推送至棧頂。
- istore,將棧頂 int 型數值存入指定局部變數。同理astore_i代表將棧頂引用型數值存入第i個局部變數。
- dup,複製棧頂數值並將複製值壓入棧頂。
- invokevirtual,調用實例方法。
- invokespecial,調用超類構造方法,實例初始化方法,私有方法。
- invokestatic,調用靜態方法。
- invokeinterface,調用介面方法。
- invokedynamic,調用動態鏈接方法。
- new,創建一個對象,並將其引用值壓入棧頂。
總結
到底創建了幾個對象呢?
-
如果xyz不存在,引用算對象的話,那就是3個
-
如果xyz不存在,引用不算對象的話,那就是2個
-
如果xyz存在,引用算對象的話,那就是2個
-
如果xyz存在,引用不算對象的話,那就是1個
當然,我認為引用肯定是不算對象的,最終答案應該是1或者2個,這個面試題說實話不應該出現在初級面試題里。
另外,如果你不看不懂反編譯後的位元組碼指令,關注公眾號回復關鍵字111可獲取《Java虛擬機規範》電子書。