欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

如何重寫hashcode和equals方法

 更新時間:2022年06月21日 11:03:10   作者:簡樂君  
這篇文章主要介紹了如何重寫hashcode和equals方法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

如何重寫hashcode和equals方法

我們都知道,要比較兩個對象是否相等時需要調(diào)用對象的equals()方法,即判斷對象引用所指向的對象地址是否相等,對象地址相等時,那么與對象相關(guān)的對象句柄、對象頭、對象實例數(shù)據(jù)、對象類型數(shù)據(jù)等也是完全一致的,所以我們可以通過比較對象的地址來判斷是否相等。

Object源碼理解

對象在不重寫的情況下使用的是Object的equals方法和hashcode方法,從Object類的源碼我們知道,默認的equals 判斷的是兩個對象的引用指向的是不是同一個對象;而hashcode也是根據(jù)對象地址生成一個整數(shù)數(shù)值;

另外我們可以看到Object的hashcode()方法的修飾符為native,表明該方法是否操作系統(tǒng)實現(xiàn),java調(diào)用操作系統(tǒng)底層代碼獲取哈希值。

public class Object { 
public native int hashCode(); 
    /**
     * Indicates whether some other object is "equal to" this one.
     * <p>
     * The {@code equals} method implements an equivalence relation
     * on non-null object references:
     * <ul>
     * <li>It is <i>reflexive</i>: for any non-null reference value
     *     {@code x}, {@code x.equals(x)} should return
     *     {@code true}.
     * <li>It is <i>symmetric</i>: for any non-null reference values
     *     {@code x} and {@code y}, {@code x.equals(y)}
     *     should return {@code true} if and only if
     *     {@code y.equals(x)} returns {@code true}.
     * <li>It is <i>transitive</i>: for any non-null reference values
     *     {@code x}, {@code y}, and {@code z}, if
     *     {@code x.equals(y)} returns {@code true} and
     *     {@code y.equals(z)} returns {@code true}, then
     *     {@code x.equals(z)} should return {@code true}.
     * <li>It is <i>consistent</i>: for any non-null reference values
     *     {@code x} and {@code y}, multiple invocations of
     *     {@code x.equals(y)} consistently return {@code true}
     *     or consistently return {@code false}, provided no
     *     information used in {@code equals} comparisons on the
     *     objects is modified.
     * <li>For any non-null reference value {@code x},
     *     {@code x.equals(null)} should return {@code false}.
     * </ul>
     * <p>
     * The {@code equals} method for class {@code Object} implements
     * the most discriminating possible equivalence relation on objects;
     * that is, for any non-null reference values {@code x} and
     * {@code y}, this method returns {@code true} if and only
     * if {@code x} and {@code y} refer to the same object
     * ({@code x == y} has the value {@code true}).
     * <p>
     * Note that it is generally necessary to override the {@code hashCode}
     * method whenever this method is overridden, so as to maintain the
     * general contract for the {@code hashCode} method, which states
     * that equal objects must have equal hash codes.
     *
     * @param   obj   the reference object with which to compare.
     * @return  {@code true} if this object is the same as the obj
     *          argument; {@code false} otherwise.
     * @see     #hashCode()
     * @see     java.util.HashMap
     */
    public boolean equals(Object obj) {
        return (this == obj);
    }
}

需要重寫equals()的場景

假設(shè)現(xiàn)在有很多學生對象,默認情況下,要判斷多個學生對象是否相等,需要根據(jù)地址判斷,若對象地址相等,那么對象的實例數(shù)據(jù)一定是一樣的,但現(xiàn)在我們規(guī)定:當學生的姓名、年齡、性別相等時,認為學生對象是相等的,不一定需要對象地址完全相同,例如學生A對象所在地址為100,學生A的個人信息為(姓名:A,性別:女,年齡:18,住址:北京軟件路999號,體重:48),學生A對象所在地址為388,學生A的個人信息為(姓名:A,性別:女,年齡:18,住址:廣州暴富路888號,體重:55),這時候如果不重寫Object的equals方法,那么返回的一定是false不相等,這個時候就需要我們根據(jù)自己的需求重寫equals()方法了。

package jianlejun.study; 
public class Student {
	private String name;// 姓名
	private String sex;// 性別
	private String age;// 年齡
	private float weight;// 體重
	private String addr;// 地址
	
	// 重寫hashcode方法
	@Override
	public int hashCode() {
		int result = name.hashCode();
		result = 17 * result + sex.hashCode();
		result = 17 * result + age.hashCode();
		return result;
	}
 
	// 重寫equals方法
	@Override
	public boolean equals(Object obj) {
		if(!(obj instanceof Student)) {
       // instanceof 已經(jīng)處理了obj = null的情況
			return false;
		}
		Student stuObj = (Student) obj;
		// 地址相等
		if (this == stuObj) {
			return true;
		}
		// 如果兩個對象姓名、年齡、性別相等,我們認為兩個對象相等
		if (stuObj.name.equals(this.name) && stuObj.sex.equals(this.sex) && stuObj.age.equals(this.age)) {
			return true;
		} else {
			return false;
		}
	}
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
	public String getSex() {
		return sex;
	}
 
	public void setSex(String sex) {
		this.sex = sex;
	}
 
	public String getAge() {
		return age;
	}
 
	public void setAge(String age) {
		this.age = age;
	}
 
	public float getWeight() {
		return weight;
	}
 
	public void setWeight(float weight) {
		this.weight = weight;
	}
 
	public String getAddr() {
		return addr;
	}
 
	public void setAddr(String addr) {
		this.addr = addr;
	}
 
}

現(xiàn)在我們寫個例子測試下結(jié)果:

public static void main(String[] args) {
    Student s1 =new Student();
    s1.setAddr("1111");
    s1.setAge("20");
    s1.setName("allan");
    s1.setSex("male");
    s1.setWeight(60f);
    Student s2 =new Student();
    s2.setAddr("222");
    s2.setAge("20");
    s2.setName("allan");
    s2.setSex("male");
    s2.setWeight(70f);
    if(s1.equals(s2)) {
        System.out.println("s1==s2");
    }else {
        System.out.println("s1 != s2");
    }
}

在重寫了student的equals方法后,這里會輸出s1 == s2,實現(xiàn)了我們的需求,如果沒有重寫equals方法,那么上段代碼必定輸出s1!=s2。

通過上面的例子,你是不是會想,不是說要同時重寫Object的equals方法和hashcode方法嗎?那上面的例子怎么才只用到equals方法呢,hashcode方法沒有體現(xiàn)出來,不要著急,我們往下看。

需要重寫hashcode()的場景

以上面例子為基礎(chǔ),即student1和student2在重寫equals方法后被認為是相等的。

在兩個對象equals的情況下進行把他們分別放入Map和Set中

在上面的代碼基礎(chǔ)上追加如下代碼:

Set set = new HashSet();
	set.add(s1);
	set.add(s2);
	System.out.println(set);

如果沒有重寫Object的hashcode()方法(即去掉上面student類中hashcode方法塊),這里會輸出

[jianlejun.study.Student@7852e922, jianlejun.study.Student@4e25154f]

說明該Set容器類有2個元素。.........等等,為什么會有2個元素????剛才經(jīng)過測試,s1不是已經(jīng)等于s2了嗎,那按照Set容器的特性會有一個去重操作,那為什么現(xiàn)在會有2個元素。這就涉及到Set的底層實現(xiàn)問題了,這里簡單介紹下就是HashSet的底層是通過HashMap實現(xiàn)的,最終比較set容器內(nèi)元素是否相等是通過比較對象的hashcode來判斷的?,F(xiàn)在你可以試試吧剛才注釋掉的hashcode方法弄回去,然后重新運行,看是不是很神奇的就只輸出一個元素了

@Override
	public int hashCode() {
		int result = name.hashCode();
		result = 17 * result + sex.hashCode();
		result = 17 * result + age.hashCode();
		return result;
	}

或許你會有一個疑問?hashcode里的代碼該怎么理解?該如何寫?其實有個相對固定的寫法,先整理出你判斷對象相等的屬性,然后取一個盡可能小的正整數(shù)(盡可能小時怕最終得到的結(jié)果超出了整型int的取數(shù)范圍),這里我取了17,(好像在JDK源碼中哪里看過用的是17),然后計算17*屬性的hashcode+其他屬性的hashcode,重復步驟。

重寫hashcode方法后輸出的結(jié)果為:

[jianlejun.study.Student@43c2ce69]

同理,可以測試下放入HashMap中,key為<s1,s1>,<s2,s2>,Map也把兩個同樣的對象當成了不同的Key(Map的Key是不允許重復的,相同Key會覆蓋)那么沒有重寫的情況下map中也會有2個元素,重寫的情況會最后put進的元素會覆蓋前面的value

Map m = new HashMap();
	m.put(s1, s1);
	m.put(s2, s2);
	System.out.println(m);
	System.out.println(((Student)m.get(s1)).getAddr());
 
輸出結(jié)果:
{jianlejun.study.Student@43c2ce69=jianlejun.study.Student@43c2ce69}
222

可以看到最終輸出的地址信息為222,222是s2成員變量addr的值,很明天,s2已經(jīng)替換了map中key為s1的value值,最終的結(jié)果是map<s1,s2>。即key為s1value為s2.

原理分析

因為我們沒有重寫父類(Object)的hashcode方法,Object的hashcode方法會根據(jù)兩個對象的地址生成對相應(yīng)的hashcode;

s1和s2是分別new出來的,那么他們的地址肯定是不一樣的,自然hashcode值也會不一樣。

Set區(qū)別對象是不是唯一的標準是,兩個對象hashcode是不是一樣,再判定兩個對象是否equals;

Map 是先根據(jù)Key值的hashcode分配和獲取對象保存數(shù)組下標的,然后再根據(jù)equals區(qū)分唯一值(詳見下面的map分析)

補充HashMap知識

  • hashMap組成結(jié)構(gòu):hashMap是由數(shù)組和鏈表組成;
  • hashMap的存儲:一個對象存儲到hashMap中的位置是由其key 的hashcode值決定的;查hashMap查找key: 找key的時候hashMap會先根據(jù)key值的hashcode經(jīng)過取余算法定位其所在數(shù)組的位置,再根據(jù)key的equals方法匹配相同key值獲取對應(yīng)相應(yīng)的對象;

案例:

(1)hashmap存儲

存值規(guī)則:把Key的hashCode 與HashMap的容量 取余得出該Key存儲在數(shù)組所在位置的下標(源碼定位Key存儲在數(shù)組的哪個位置是以hashCode & (HashMap容量-1)算法得出)這里為方便理解使用此方式;

//為了演示方便定義一個容量大小為3的hashMap(其默認為16)

HashMap map=newHashMap(3);

map.put("a",1); 得到key 為“a” 的hashcode 值為97然后根據(jù) 該值和hashMap 容量取余97%3得到存儲位到數(shù)組下標為1;

map.put("b",2); 得到key 為“b” 的hashcode 值為98,98%3到存儲位到數(shù)組下標為2;

map.put("c",3); 得到key 為“c” 的hashcode 值為99,99%3到存儲位到數(shù)組下標為0;

map.put("d",4); 得到key 為“d” 的hashcode 值為100,100%3到存儲位到數(shù)組下標為1;

map.put("e",5); 得到key 為“e” 的hashcode 值為101,101%3到存儲位到數(shù)組下標為2;

map.put("f",6); 得到key 為“f” 的hashcode 值為102,102%3到存儲位到數(shù)組下標為0;

(2)hashmap的查找key

得到key在數(shù)組中的位置:根據(jù)上圖,當我們獲取key 為“a”的對象時,那么我們首先獲得 key的hashcode97%3得到存儲位到數(shù)組下標為1;

匹配得到對應(yīng)key值對象:得到數(shù)組下表為1的數(shù)據(jù)“a”和“c”對象, 然后再根據(jù) key.equals()來匹配獲取對應(yīng)key的數(shù)據(jù)對象;

hashcode 對于HashMapde:如果沒有hashcode 就意味著HashMap存儲的時候是沒有規(guī)律可尋的,那么每當我們map.get()方法的時候,就要把map里面的對象一一拿出來進行equals匹配,這樣效率是不是會超級慢;

hashcode方法文檔說明

在equals方法沒被修改的前提下,多次調(diào)用同一對象的hashcode方法返回的值必須是相同的整數(shù);

如果兩個對象互相equals,那么這兩個對象的hashcode值必須相等;

為不同對象生成不同的hashcode可以提升哈希表的性能;

重寫hashCode和equals方法的一些思考

經(jīng)常能看到重寫equals方法就需要重寫hashCode方法的說法,這點也很好理解,假如重寫equals使得兩個對象通過equals判斷為真 ,但是如果hashCode計算出來的值如果不一樣,就會發(fā)生矛盾,就是明明兩個對象是一樣的,但是卻會被映射到不同位置,這樣子的話,hashMap或者hashSet之類的哈希結(jié)構(gòu)就會存儲多個相同的對象。

還可以通過一個例子理解

		Map<String,Value> map1 = new HashMap<String,Value>();
        String s1 = new String("key");
        String s2 = new String("key");
        Value value = new Value(2);
        map1.put(s1, value);
        System.out.println("s1.equals(s2):"+s1.equals(s2));
        System.out.println("map1.get(s1):"+map1.get(s1));
        System.out.println("map1.get(s2):"+map1.get(s2));

        Map<Key,Value> map2 = new HashMap<Key,Value>();
        Key k1 = new Key("A");
        Key k2 = new Key("A");
        map2.put(k1, value);
        System.out.println("k1.equals(k2):"+k1.equals(k2));
        System.out.println("map2.get(k1):"+map2.get(k1));
        System.out.println("map2.get(k2):"+map2.get(k2));

Key和Value的類定義如下

static class Key{
        private String k;
        public Key(String key){
            this.k=key;
        }
        //如果不重寫hashCode,只重寫了equals,會造成相同值被放入不同的桶中
//        @Override
//        public int hashCode() {
//            return k.hashCode();
//        }

        @Override
        public boolean equals(Object obj) {
            if(obj instanceof Key){
                Key key=(Key)obj;
                return k.equals(key.k);
            }
            return false;
        }
    }
    
 static class Value{
        private int v;

        public Value(int v){
            this.v=v;
        }

        @Override
        public String toString() {
            return "類Value的值-->"+v;
        }
    }

輸出結(jié)果如下

可以看出,如果重寫了equals但不重寫hashCode的話,會出現(xiàn)相同的對象會被map判斷成不同對象,導致可以重復插入多個相同對象。

除此之外,還會思考如果重寫hashCode但不重寫equals方法的情況下,又會造成什么問題,因此用以下例子說明

		Map<Integer, Integer> map3 =new HashMap();
        while (true){
            boolean flag = false;
            for (int i = 0; i < 1000; i++) {
                if(!map3.containsKey(i)){
                    map3.put(i, i);
                    flag = true;
                }
            }
            if (flag == false) {
                break;
            }
            System.out.println("map3的容量" + map3.size());
        }

        Map<Key2, Integer> map4 =new HashMap();
        while (true){
            boolean flag = false;
            for (int i = 0; i < 1000; i++) {
                if(!map4.containsKey(new Key2(i))){
                    map4.put(new Key2(i), i);
                    flag = true;
                }
            }
            if (flag == false) {
                break;
            }
            System.out.println("map4的容量" + map4.size());
        }

Key2的類定義如下

    static class Key2{
        Integer id;

        Key2(Integer id) {
            this.id = id;
        }

        @Override
        public int hashCode() {
            return id.hashCode();
        }
        //不重寫equals就會導致一直認為沒有相同的值,就會一直插入。
//        @Override
//        public boolean equals(Object obj) {
//            if(obj instanceof Key2){
//                Key2 key2 =(Key2)obj;
//                return id.equals(key2.id);
//            }
//            return false;
//        }
    }

結(jié)果如下

從圖中結(jié)果可以看出,map4一直在添加數(shù)據(jù),說明map一直認為沒有相同的key對象,因此對于同一個i,不重寫的equals永遠不會判斷相同,所以會一直插入。因此hashCode和equals必須全部重寫,任何一個不重寫都會發(fā)生錯誤。

到這里也還會思考,String和Integer的和hashCode和equals是怎么計算的

Integer的hashCode計算如下

可以看出是直接返回原始值  

String的hashCode計算如下

可以由注釋看出來,計算的結(jié)果就是s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1],就比如字符串“abc”,a的ascll碼是97,b是98,c是99,因此該字符串的hashCode值就是(97 *31 + 98)*31 + 99,這里引出一點思考:為什么用31呢?,查閱資料得知因為31是一個質(zhì)數(shù),可以使得減少哈希算法的沖突概率,同時31的二進制數(shù)是11111,因此31 *i就等于(i << 5) - i,可以優(yōu)化運算。

Integer的equals計算如下

就是說明,只需要使用instanceof判斷傳入對象是否是Integer的實例或者子類,是就強轉(zhuǎn)成Integer類,然后判斷值是否相等

String的equals計算如下

這里首先用“==”比較了equals兩邊對象,如果一樣直接返回true,然后就是用instanceof判斷是否是String實例或者子類,如果是就強轉(zhuǎn),然后再根據(jù)數(shù)組長度判斷是否相同,如果相同就遍歷數(shù)組每個元素,都相同就返回true,其他情況都返回false。

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java簡單實現(xiàn)銀行ATM系統(tǒng)

    Java簡單實現(xiàn)銀行ATM系統(tǒng)

    這篇文章主要為大家詳細介紹了Java簡單實現(xiàn)銀行ATM系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • SpringBoot整合RedisTemplate實現(xiàn)緩存信息監(jiān)控的步驟

    SpringBoot整合RedisTemplate實現(xiàn)緩存信息監(jiān)控的步驟

    這篇文章主要介紹了SpringBoot整合RedisTemplate實現(xiàn)緩存信息監(jiān)控,一步一步的實現(xiàn)?Springboot?整合?Redis?來存儲數(shù)據(jù),讀取數(shù)據(jù),需要的朋友可以參考下
    2022-01-01
  • java中Hutool工具類的常見使用場景詳解

    java中Hutool工具類的常見使用場景詳解

    在日常開發(fā)中,我們會使用很多工具類來提升項目開發(fā)的速度,而國內(nèi)用的比較多的 Hutool 框架,就是其中之一,本文我們就來介紹一下Hutool的具體使用吧
    2023-12-12
  • Java實現(xiàn)添加文字水印和圖片水印功能

    Java實現(xiàn)添加文字水印和圖片水印功能

    為圖片添加水印是一種常用的圖片處理技術(shù),本文主要介紹了Java實現(xiàn)添加文字水印和圖片水印功能,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-05-05
  • Java實現(xiàn)簡單猜數(shù)字小游戲

    Java實現(xiàn)簡單猜數(shù)字小游戲

    這篇文章主要為大家詳細介紹了Java實現(xiàn)猜數(shù)字游戲,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-12-12
  • Java8新特性之深入解析日期和時間_動力節(jié)點Java學院整理

    Java8新特性之深入解析日期和時間_動力節(jié)點Java學院整理

    這篇文章主要介紹了Java8新特性之深入解析日期和時間_動力節(jié)點Java學院整理,需要的朋友可以參考下
    2017-06-06
  • 深入理解Java8新特性之Stream API的創(chuàng)建方式和中間操作步驟

    深入理解Java8新特性之Stream API的創(chuàng)建方式和中間操作步驟

    Stream是Java8的一大亮點,是對容器對象功能的增強,它專注于對容器對象進行各種非常便利、高效的 聚合操作(aggregate operation)或者大批量數(shù)據(jù)操作。Stream API借助于同樣新出現(xiàn)的Lambda表達式,極大的提高編程效率和程序可讀性,感興趣的朋友快來看看吧
    2021-11-11
  • Mybatis在sqlite中無法讀寫byte[]類問題的解決辦法

    Mybatis在sqlite中無法讀寫byte[]類問題的解決辦法

    這篇文章主要給大家介紹了關(guān)于Mybatis在sqlite中無法讀寫byte[]類問題的解決辦法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-10-10
  • 在Spring 中使用@Aspect 控制自定義注解的操作

    在Spring 中使用@Aspect 控制自定義注解的操作

    這篇文章主要介紹了在Spring 中使用@Aspect 控制自定義注解的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-01-01
  • Java異常:java.net.UnknownHostException產(chǎn)生的原因和解決方案

    Java異常:java.net.UnknownHostException產(chǎn)生的原因和解決方案

    這篇文章主要給大家介紹了關(guān)于Java異常:java.net.UnknownHostException產(chǎn)生的原因和解決方案,這個異常是java.net包中的一部分,具體說它是類的一個實例,異常通常是由主機名無法解析為IP地址引起的,文中將解決的辦法介紹的非常詳細,需要的朋友可以參考下
    2024-01-01

最新評論