關於java中的==,equals()

1. 先從一道面試題提及

請問下面的java

public class Demo {
    public static void main(String args[]){
        String a = "a" + "b" + 1;
        String b = "ab1";
        System.out.println(a == b);
    }
}

要了解這個問題,須要回答下面的幾個問題:面試

  1. 關於「 ==」是作什麼的?
  2. equals 呢?
  3. a和b在內存中是什麼樣的?
  4. 編譯時優化方案。

2. 關於==

在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

3. 關於「equals()」方法

「equals()」方法,首先是在Object類中被定義的,它的定義中就是使用「==」方式來匹配的。jvm

//equals在Object類中的源碼
public boolean equals(Object obj) {
    return (this == obj);
}

也就是說,若是不去重寫equals()方法,而且對應的類其父類中沒有重寫過equals()方法,那麼默認的equals()操做就是對比對象的地址。優化

equals()方法之因此存在,是但願子類去重寫這個方法,實現對比值的功能。ui

3. a和b在內存中是什麼樣的?

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不可變,因此編譯器天然認爲結果是不可變的。

4. 內存中的字符串(詳細解釋)

字符串對象內部是用字符數組存儲的,那麼看下面的例子:

String m = "hello,world";
String n = "hello,world";
String u = new String(m);
String v = new String("hello,world");

這些語句會發生什麼事情?大概是這樣的:

  1. 會分配一個11長度的char數組,並在常量池分配一個由這個char數組組成的字符串,而後由m去引用這個字符串。
  2. 用n去引用常量池裏邊的字符串,因此和m引用的是同一個對象
  3. 生成一個新的字符串,單內部的字符數組引用着m內部的字符數組。
  4. 一樣會生成一個新的字符串,但內部的字符數組引用常量池裏邊的字符串內部的字符數組,意思是和u是一樣的字符數組。

咱們使用圖來表示的話,狀況就大概是這樣的:

image

結論就是,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,但一樣是能夠經過反射進行修改。

5. 關於String中的intern方法

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()操做後用等號是能夠匹配的。

相關文章
相關標籤/搜索