Java基礎中==、equals、hashcode方法詳細解讀與測試

一.基礎知識:

  • 如果一個變量指向的數據是對象類型,這時候涉及兩塊內存,對象本身佔用一塊內存(堆內存),變量也佔用一塊內存。例如:Object o = new Object(); 變量o佔一個內存,new Object()是佔另一個內存。通俗來講:new Object()創造了一個對象,類型是Object,而Object o則創造了一個可以存儲Object類型對象引用的“引用存儲器”,而等號=則把new Object()這個對象的引用存到了o之中,這樣就可以用a來使用這個對象數據和方法了。

  • 舉一個案例(圖示):

//構造方法創建   ---new
String s1 = new String("aaa");//這裏把"aaa"代表的對象的引用存到了s1中
s1= new String("bbb");//這裏又把另一個對象"bbb"存到了s1中,所以s1就不是指向原來的"aaa"了,而是指向"bbb"了,新的空間, aaa等着回收

創建對象

二.equals和==的區別

  • 測試的代碼有兩個類,Test類和User類,可以自己粘貼測試一下
  • Test類:
import java.util.HashSet;

public class Test {
    public static void main(String[] args) {
        test_eq();    
//        test_hashcode();    
    }

    public static void test_eq() {
        int a = 1;
        int b = 1;
        System.out.println(a == b);  //true 比較的是值

        User u1 = new User("tom",12,1001);
        User u2 = new User("tom",12,1001);
        System.out.println(u1+","+u2);  //User@1b6d3586,User@4554617c
        //這裏==比較的是變量(棧)內存中存放的對象的(堆)內存地址,用來判斷兩個對象的地址是否相同,即是否是指相同一個對象
        System.out.println(u1 == u2);   //false   比較的是地址,不是值
        System.out.println(u1.equals(u2));

        //String類中對equals方法進行了重寫,字符串與字符串比較的是值
        String s1 = "jack"; //字符常量,放在常量池
        String s2 = "jack"; //創建流程:先判斷常量池是否存在jack,如果有,就直接指向,如果沒有則再創建一個對象
        System.out.println(s1.equals(s2));  //true 字符串  實際調用的是String類中重寫的方法,比較的是值
    }

    private static void test_hashcode() {
        User u1 = new User("tom",12,1001);
        User u2 = new User("tom",12,1001);
        HashSet<Object> set = new HashSet<>();
        set.add(u1);
        set.add(u2);
        System.out.println(u1.hashCode());  //110571150
        System.out.println(u2.hashCode());  //110571150
        System.out.println(u1.hashCode()==u2.hashCode());   //true
        System.out.println(Integer.toHexString(u1.hashCode())); //6972e8e
        System.out.println(u1); // User@6972e8e  包名+類名 + @ + 16進制的hashCode值

        System.out.println(u1.equals(u2));

    }

}

  • User類:
import java.util.Objects;

public class User {
    private String name;
    private int age;
    private int id;

    public User(String name, int age, int id) {
        this.name = name;
        this.age = age;
        this.id = id;
    }

    public User() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    //按快捷鍵自動生成equals和hashcode方法
//    @Override
//    public boolean equals(Object o) {
//        if (this == o) return true;
//        if (o == null || getClass() != o.getClass()) return false;
//        User user = (User) o;
//        return age == user.age &&
//                id == user.id &&
//                Objects.equals(name, user.name);
//    }
//
//    @Override
//    public int hashCode() {
//        return Objects.hash(name, age, id);
//    }

    //這個equals()方法主要測試equals和==的
    @Override
	public boolean equals(Object obj) {
        //instanceof運算符是用來在運行時指出對象是否是特定類的一個實例,instanceof通過返回一個布爾值指出true或者false
        //在這裏是Test類創建的User類的u1和u2是否是User的實例,所以要判斷,通俗來講:你不能在大街上隨便拉一個人做手術,必須要有醫師執照
		if(obj instanceof User) {
		    //屬於引用類型的強制轉換,將父類類型轉爲子類,向下轉後,可以訪問子類特有的屬性和方法
			User u = (User)obj;
			//這裏equals和==是一樣的,都是比較的是值
			if(this.name.equals(u.getName()) && this.age == u.getAge() && this.id==u.getId()) {
				return true;
			}
		}

		return false;
	}
}


  • ==判斷的是是否引用同一個對象(判斷基本類型比較的是值,判斷引用類型比較的是地址)
		int a = 1;
        int b = 1;
        System.out.println(a == b);  //true 比較的是值 
  • 基本類型有8個(byte,short,int,long,float,double,boolean,char),基本形式:數據類型 變量名 = 值,輸出時顯示的是具體的值
		User u1 = new User("tom",12,1001);
        User u2 = new User("tom",12,1001);
        System.out.println(u1+","+u2);  //User@1b6d3586,User@4554617c
        System.out.println(u1 == u2);   //false   比較的是地址,不是值
  • 這裏==比較的是變量(棧)內存中存放的對象的(堆)內存地址,用來判斷兩個對象的地址是否相同,即是否是指相同一個對象
  • 引用類型:除了基本類型以外都是引用類型,如String 數組 類 接口 枚舉 基本形式:數據類型 名稱 = new 數據類型(),輸出的顯示的是內存地址
		User u1 = new User("tom",12,1001);
        User u2 = new User("tom",12,1001);
        System.out.println(u1+","+u2);  //User@1b6d3586,User@4554617c
        System.out.println(u1 == u2);   //false   比較的是地址,不是值
        System.out.println(u1.equals(u2));//false
  • 上述代碼增加了一行equals的比較

1. 沒有在User類重寫equals()方法

  • 如果equals沒有被重寫,默認和“==”一樣。也就是User類中沒有重寫equals方法,只寫了用戶類的屬性,有參和無參構造,還有get和set方法,其餘沒有。
  • 這時候運行出來的結果是false,在User類中沒有重寫equals方法之前,比較的是地址

2.在User類重寫equals()方法

  • User類:
//    @Override
//    public boolean equals(Object o) {
//        if (this == o) return true;
//        if (o == null || getClass() != o.getClass()) return false;
//        User user = (User) o;
//        return age == user.age &&
//                id == user.id &&
//                Objects.equals(name, user.name);
//    }
//
//    @Override
//    public int hashCode() {
//        return Objects.hash(name, age, id);
//    }

    @Override
	public boolean equals(Object obj) {
        //instanceof運算符是用來在運行時指出對象是否是特定類的一個實例,instanceof通過返回一個布爾值指出true或者false
        //在這裏是Test類創建的User類的u1和u2是否是User的實例,所以要判斷,通俗來講:你不能在大街上隨便拉一個人做手術,必須要有醫師執照
		if(obj instanceof User) {
		    //屬於引用類型的強制轉換,將父類類型轉爲子類,向下轉後,可以訪問子類特有的屬性和方法
			User u = (User)obj;
			//這裏equals和==是一樣的,都是比較的是值
			if(this.name.equals(u.getName()) && this.age == u.getAge() && this.id==u.getId()) {
				return true;
			}
		}

		return false;
	}
  • Test類:
		User u1 = new User("tom",12,1001);
        User u2 = new User("tom",12,1001);
        System.out.println(u1+","+u2);  //User@1b6d3586,User@4554617c
        System.out.println(u1 == u2);   //false   比較的是地址,不是值
        System.out.println(u1.equals(u2));//true
  • 上述代碼是User類中重寫equals方法後(在重寫equals方法時,系統會自動將equals和hashCode兩個方法都重寫了),這時如果想用系統自動生成的equals方法進行測試,可以先將hashCode方法給註釋掉。這裏我用的是自己寫的equals方法進行的測試,再運行程序時,equals比較出來的是true

提醒:equals方法的重寫,用系統的或者用自己寫的equals方法,性質都是一樣的,只能使用其中一個equals()方法。

  • 源碼equals()方法:
    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
  • 因爲這時候是按照重寫後的規則進行比較,而源碼equals方法返回的是“==”的值,比較的是值或者內容是否相等。如果將u1中的name值改變,equals返回的就是false,因爲u1和u2實例內容不一樣呢。
		String s1 = "jack"; //字符常量,放在常量池
        String s2 = "jack"; //創建流程:先判斷常量池是否存在jack,如果有,就直接指向,如果沒有則再創建一個對象
        System.out.println(s1.equals(s2));  //true 字符串  實際調用的是String類中重寫的方法,比較的是值
  • 看一個知識:因爲String類經常會被用到,所以String類中對equals方法就已經進行了重寫,字符串與字符串比較的是值
  • String常量:使用雙引號直接創建的字符串,稱爲字符常量,字符常量放在內存中的常量池,在我的下一篇博客中已經寫了關於常量池的介紹。
  • 代碼常量池示意圖
    常量池

總結:

  • == 判斷的是是否引用同一個對象( 判斷基本類型比較的是值,判斷引用類型比較的是地址 )
  • 如果equals 沒有被重寫,默認和 == 一樣
    String類中對equals 方法進行了重寫,字符串與字符串比較的是值
  • 如果重寫了equals 方法,則按照重寫後的規則進行比較
  • 自定義類可以重寫equals方法,實現對特定屬性的比較

三.equals和hashcode的使用:

  • 爲什麼要使用hashCode():
    java.lang.Object 類 ,所有類的根,所有類都直接或者間接的繼承了Object類。其中hashcode()和equals()方法,經常會用到。
    集合Set中的元素是無序不可重複,那判斷兩個元素是否重複的依據是什麼呢?
    有小夥伴說:比較對象是否相等當然用Object類中的equals()方法啊。但是,如果Set集合中存在大量對象,後添加到集合中Set的對象元素比較次數會逐漸增多,大大的降低了程序運行效率。Java中採用哈希算法(也叫散列算法)來解決這個問題,將對象(或數據)依特定算法直接映射到一個地址上,對象的存取效率大大提高。
		User u1 = new User("tom",12,1001);
        User u2 = new User("tom",12,1001);
        HashSet<Object> set = new HashSet<>();
        set.add(u1);
        set.add(u2);
        System.out.println(u1.hashCode());  //110571150
        System.out.println(u2.hashCode());  //110571150
        System.out.println(u1.hashCode()==u2.hashCode());   //true
        System.out.println(Integer.toHexString(u1.hashCode())); //6972e8e
        System.out.println(u1); // User@6972e8e  包名+類名 + @ + 16進制的hashCode值

        System.out.println(u1.equals(u2));

1.equals()方法註釋掉,使用hashCode()方法

//    @Override
//    public boolean equals(Object o) {
//        if (this == o) return true;
//        if (o == null || getClass() != o.getClass()) return false;
//        User user = (User) o;
//        return age == user.age &&
//                id == user.id &&
//                Objects.equals(name, user.name);
//    }
//
    @Override
    public int hashCode() {
        return Objects.hash(name, age, id);
    }
  • 以上這個示例,我們只是重寫了hashCode方法,u1和u2通過equals()方法比較返回的是false
  • 從上面的結果可以看出:雖然兩個對象的hashCode相等,但是實際上兩個對象並不是相等;,我們沒有重寫equals方法,那麼就會調用object默認的equals方法,是比較兩個對象的引用是不是相同,顯示這是兩個不同的對象(堆內存),兩個對象的引用肯定是不定的。這裏我們將生成的對象放到了HashSet中,而HashSet中只能夠存放唯一的對象,也就是相同的(適用於equals方法)的對象只會存放一個,但是這裏實際上是兩個對象a,b都被放到了HashSet中,這樣HashSet就失去了他本身的意義了。

2.User類同時使用equals()方法和hashCode()方法

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return age == user.age &&
                id == user.id &&
                Objects.equals(name, user.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, id);
    }
  • 以上這個示例,我們同時重寫了equals和hashCode方法,這時u1和u2通過equals()方法比較返回的是true

  • 從上面的結果可以看出:把User類中的equals方法寫上,比較的就是兩個對象的內容是否相等(也就是比較的是值,不是地址)

  • 那集合(Set)如何判重呢?

    • 添加元素時,調用該對象的hashCode方法,獲取一個哈希值
    • 根據哈希值,使用哈希算法(有好幾種算法,一般用除留取餘法)通過餘數確定一個哈希表中的位置
    • 如果該位置沒有元素,說明此對象是第一次存儲到集合Set中,則直接將此對象存儲在此位置中
    • 如果該位置有元素了,則繼續調用兩個對象的equals()方法進行比較
      - true:則認爲兩個對象爲同一個對象,無法添加,捨棄當前對象
      - false:則認爲兩個對象不是同一個對象,以鏈表結構向當前位置進行追加,這屬於哈希表中解決衝突的方法稱爲:鏈地址法。還有一個方法叫線性探查法,這屬於數據結構中哈希表中的知識。
  • 通俗來講:有兩個人名字一樣(hashCode()值一樣),你不能說他們是同一個人。名字相同,還要通過equals()判斷兩個人是否相等。

  • 下面是我學習數據結構中看視頻截的圖,哈希表解決衝突的線性探查法和鏈地址法講解圖示:
    線性探查法
    鏈地址法

  • 這裏首先明白一個問題:
    如果兩個對啊ing的equals方法比較爲true時,則兩個對象的hashCode值也一定相同。先判斷hashCode(),然後再判斷equals()。如果兩個對象的equals方法比較爲false,卻不能證明兩個對象的hashCode值可以不相同,也可以相同。

總結:

  • 判斷重複的依據,兩個對象hashCode值一致,並且equals也返回true— 同一個對象–集合中只添加一個進入
  • 在set結合中防止相同對象被添加,需要hashCode 和equals都重寫

如果對你有幫助,點個贊吧0.0
若有不正之處,請多多諒解並歡迎批評指正,不甚感激

發佈了24 篇原創文章 · 獲贊 12 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章