請問下面的java
public class Demo { public static void main(String args[]){ String a = "a" + "b" + 1; String b = "ab1"; System.out.println(a == b); } }
要了解這個問題,須要回答下面的幾個問題:面試
在Java語言中,「==」就是對比兩個內存單元的內容是否同樣。
若是是原始類型byte,boolean,short,char,int,long,float,double,就是直接比較它們的值。
若是是引用,比較的就是引用的值,「引用的值」能夠被認爲對象的邏輯地址。若是兩個引用發生「==」操做,就是比較相應的兩個對象的地址值是否同樣。換句話說,若是兩個引用所保存的對象是同一個對象,則返回true,不然返回false(若是引用指向的是null,其實這也是一個jvm賦予給它的某個指定的值)。
看下面的代碼:數組
public class Demo { public static void main(String args[]){ List<String> a = null; List<String> b = null; System.out.println(a == b); } } // 輸出結果 true
「equals()」方法,首先是在Object類中被定義的,它的定義中就是使用「==」方式來匹配的。jvm
//equals在Object類中的源碼 public boolean equals(Object obj) { return (this == obj); }
也就是說,若是不去重寫equals()方法,而且對應的類其父類中沒有重寫過equals()方法,那麼默認的equals()操做就是對比對象的地址。優化
equals()方法之因此存在,是但願子類去重寫這個方法,實現對比值的功能。ui
a和b在內存中是指向同一塊內存空間的。這就得益於Java的編譯時優化方案。this
咱們用反編譯軟件jd-gui看看編譯後的代碼是怎麼樣的?code
import java.io.PrintStream; public class Demo { public static void main(String[] args) { String a = "ab1"; String b = "ab1"; System.out.println(a == b); } }
看到這裏結果應該就一目瞭然了。JVM會把常量疊加在編譯時進行優化,由於常量疊加獲得的是固定的值,無須運行時再進行計算,因此會這樣優化。對象
看到這裏彆着急,JVM只會優化它能夠幫你優化的部分,它並非對全部的內容均可以優化。例如,就拿上面疊加字符串來講,若是幾個字符串疊加出現了變量,即在編譯時還不肯定具體的值是多少,那麼JVM是不會去作這樣的編譯時合併的。內存
若是上面的這段話你理解了,咱們再來看一個例子:
public class Demo { public static void main(String args[]){ String a = "a"; final String c ="a"; String b = a + "b"; String d = c + "b"; String e = getA() + "b"; String compare = "ab"; System.out.println( b == compare); System.out.println( d == compare); System.out.println( e == compare); } private static String getA(){ return "a"; } } //輸出結果: false true false
根據咱們上面的解釋,判斷b==compare和e==compare輸出結果爲false,這個比較容易理解,由於a和getA()並非一個常量,編譯時並不會對此進行優化,咱們用jd-gui可靠編譯後的代碼:
import java.io.PrintStream; public class Demo { public static void main(String[] args) { String a = "a"; String c = "a"; String b = a + "b"; String d = "ab"; String e = getA() + "b"; String compare = "ab"; System.out.println(b == compare); System.out.println(d == compare); System.out.println(e == compare); } private static String getA() { return "a"; } }
從編譯後的代碼,咱們能夠驗證咱們的結論,b和e並無被JVM優化。
比較奇怪的是變量d,被JVM優化了。區別在於對疊加的變量c有一個final修飾符。從定義上強制約束了c是不容許被改變的,因爲final不可變,因此編譯器天然認爲結果是不可變的。
字符串對象內部是用字符數組存儲的,那麼看下面的例子:
String m = "hello,world"; String n = "hello,world"; String u = new String(m); String v = new String("hello,world");
這些語句會發生什麼事情?大概是這樣的:
咱們使用圖來表示的話,狀況就大概是這樣的:
結論就是,m和n是同一個對象,但m,u,v都是不一樣的對象,但都使用了一樣的字符數組,而且用equal判斷的話也會返回true。
咱們能夠使用反射修改字符數組來驗證一下效果:
public class Demo { public static void main(String args[]) throws NoSuchFieldException, IllegalAccessException { String m = "hello,world"; String n = "hello,world"; String u = new String(m); String v = new String("hello,world"); Field f = m.getClass().getDeclaredField("value"); f.setAccessible(true); char[] cs = (char[]) f.get(m); cs[0] = 'H'; String p = "Hello,world"; System.out.println(m.equals(p)); System.out.println(n.equals(p)); System.out.println(u.equals(p)); System.out.println(v.equals(p)); } } //輸出結果: true true true true
從上面的例子能夠看到,常常說的字符串是不可變的,其實和其餘final類沒有什麼區別,仍是引用不可變的意思。雖然String類不開放value,但一樣是能夠經過反射進行修改。
public class Demo { public static void main(String args[]){ String a = "a"; String b = a + "b"; String c = "ab"; String d = new String(b); System.out.println(b == c); System.out.println(c == d); System.out.println(c == d.intern()); System.out.println(b.intern() == d.intern()); } } //輸出結果 false false true true
String引用所指向的對象,它們存儲在常量池中,同一個值的字符串保證全局惟一。
如何保證全局惟一呢? 當調用intern()方法時,JVM會在這個常量池中經過equals()方法查找是否存在等值的String,若是存在,則直接返回常量池中這個String對象的地址;若沒有找到,則會建立等值的字符串,而後再返回這個新建立空間的地址。只要是一樣的字符串,當調用intern()方法時,都會獲得常量池中對應String的引用,因此兩個字符串經過intern()操做後用等號是能夠匹配的。