Java equals和==完全解析

zhaozhangxiao發表於2021-08-03

參考:segmentfault.com/a/119000001864789...
今天朋友突然問到一個問題:

兩個物件使用x.equals(y)判斷結果為true時,兩個物件的hashCode可以不同嗎?

在Java程式設計中,判斷兩個物件是否相等常常使用equals()或是==,但是其中的區別和原理可能很多人並不完全清楚。今天就藉著上面這個問題來看看equals()==的區別和原理。

1. 資料型別與==的含義

Java中的資料型別分為基本資料型別和引用資料型別:

  1. 基本型別:程式語言中內建的最小粒度的資料型別。它包括四大類八種型別

    • 4種整數型別:byte、short、int、long
    • 2種浮點數型別:float、double
    • 1種字元型別:char
    • 1種布林型別:boolean
  2. 引用型別:引用也叫控制程式碼,引用型別,是程式語言中定義的在控制程式碼中存放著實際內容所在地址的地址值的一種資料形式

    • 介面
    • 陣列
  • 對於基本型別來說,== 比較的是它們的值
  • 對於引用型別來說,== 比較的是它們在記憶體中存放的地址(堆記憶體地址)

例:

public void test(){
    int num1 = 100;
    int num2 = 100;

    String str1 = "James";
    String str2 = "James";

    String str3 = new String("James");
    String str4 = new String("James");

    System.out.println("num1 == num2 : " + (num1 == num2));
    System.out.println("str1 address : " + System.identityHashCode(str1) + ";\nstr2 address : " + System.identityHashCode(str1) + ";\nstr1 == str2 : " + (str1 == str2));
    System.out.println("str3 address : " + System.identityHashCode(str3) + ";\nstr4 address : " + System.identityHashCode(str4) + ";\nstr3 == str4 : " + (str3 == str4));
}

執行上面的程式碼,可以得到以下結果:

num1 == num2 : true

str1 address : 1174290147;
str2 address : 1174290147;
str1 == str2 : true

str3 address : 1289696681;
str4 address : 1285044316;
str3 == str4 : false

可以看到str1和str2的記憶體地址都是1174290147,所以使用==判斷為true,但是str3和str4的地址是不同的,所以判斷為false。

2. equals() 方法解析

在Java語言中,所有類都是繼承於Object這個超類的,在這個類中也有一個equals()方法,那麼我們先來看一下這個方法。

public boolean equals(Object obj) {
    return (this == obj);
}

可以看得出,這個方法很簡單,就是比較物件的記憶體地址的。所以在物件沒有重寫這個方法時,預設使用此方法,即比較物件的記憶體地址值。但是類似於String、Integer等類均已重寫了equals()。下面以String為例。

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = length();
            if (n == anotherString.length()) {
                int i = 0;
                while (n-- != 0) {
                    if (charAt(i) != anotherString.charAt(i))
                            return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

很明顯,String的equals()方法僅僅是對比它的 資料值,而不是物件的 記憶體地址 。
以 String 為例測試一下。

public void test() {

    String str1 = "James";
    String str2 = "James";

    String str3 = new String("James");
    String str4 = new String("James");

    System.out.println("str1 address : " + System.identityHashCode(str1) + ";\nstr2 address : " + System.identityHashCode(str1) + ";\nstr1.equals(str2) : " + str1.equals(str2));
    System.out.println("str3 address : " + System.identityHashCode(str3) + ";\nstr4 address : " + System.identityHashCode(str4) + ";\nstr3.equals(str4) : " + str3.equals(str4));
}

結果為:

str1 address : 1174290147;
str2 address : 1174290147;
str1.equals(str2) : true

str3 address : 1289696681;
str4 address : 1285044316;
str3.equals(str4) : true

可以發現不管物件的記憶體地址是否相同並不影響其結果,所以String型別比較的是 資料值, 而不是 記憶體地址值。

    基本型別:對比它們的值是否相等
    引用型別:對比它們的記憶體地址是否相等
equals()
    基本型別:使用==進行比較
    引用型別:預設情況下,對比它們的地址是否相等;如果equals()方法被重寫,則根據重寫的要求來比較。
  1. equals() 與 hashCode()
    在詳細的瞭解了==和equals()的作用和區別後,現在來研究一下之前的那個問題:
    兩個物件使用x.equals(y)判斷結果為true時,兩個物件的hashCode可以不同嗎?
    首先我們需要知道hashCode到底是什麼?還是從Object這個超類來看一下。

    public int hashCode() {
     return identityHashCode(this); // 此處返回物件的記憶體地址值
    }

    程式碼也很簡單,看來預設情況下,hashCode就等於物件的 記憶體地址值(注:System.identityHashCode(Object obj)方法用於獲取物件的記憶體地址,之前的樣例程式碼中有使用)。和equals()方法一樣重寫之後,hashCode()方法方法也是可以被重寫的,而且兩者一般情況下都是成對出現。
    簡單測試一下String型別重寫hashCode()方法之後有什麼變化。

    public void test() {
     String str1 = "James";
     System.out.println("str1 address : " + System.identityHashCode(str1) + "\nstr1 hashCode : " + str1.hashCode());
    }
  2. 總結
    至此,==和equals()的區別及作用,equals()和hashCode的關係及使用已經瞭解清楚了。下面再總結一下:

     基本型別:對比它們的值是否相等
     引用型別:對比它們的記憶體地址是否相等

    equals()

     基本型別:使用==進行比較
     引用型別:預設情況下,對比它們的地址是否相等;如果equals()方法被重寫,則根據重寫的要求來比較

    對於equals()和hashCode()的關係:
    根據Object超類中的文件說明,equals()和hashCode()兩個方法應該 同進同退。上面的例子只是舉例說明存在那種情況,但那並不是一個很好的應用。

    所以一定要記住equals()和hashCode()兩個方法應該 同進同退。
    所以一定要記住equals()和hashCode()兩個方法應該 同進同退。
    所以一定要記住equals()和hashCode()兩個方法應該 同進同退。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章