【面經】被虐了之後,我翻爛了equals源碼,總結如下

来源:https://www.cnblogs.com/jiagooushi/archive/2022/08/02/16543490.html
-Advertisement-
Play Games

面試最常問的問題 1、equals比較的什麼? 2、有沒有重寫過equals? 3、有沒有重寫過hashCode? 4、什麼情況下需要重寫equals()和hashCode()? 1) equals源碼 **目標:**如果不做任何處理(可能絕大大大多數場景的對象都是這樣的),jvm對同一個對象的判斷 ...


file

面試最常問的問題

1、equals比較的什麼?

2、有沒有重寫過equals?

3、有沒有重寫過hashCode?

4、什麼情況下需要重寫equals()和hashCode()?

1) equals源碼

目標:如果不做任何處理(可能絕大大大多數場景的對象都是這樣的),jvm對同一個對象的判斷邏輯是怎樣的

我們先讀一下Object里的源碼:

    /**
     * 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>
     * 該方法用於識別兩個對象之間的相似性
     * 也就是說,對於一個非null值,x和y,當且僅當它們指向同一個對象時才會返回true
     * 言外之意,和==沒啥兩樣。
     * 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將繼承object的方法,那麼它和==也沒啥區別!

下麵一起做個面試題,驗證一下這個猜想:

package com.eq;

import java.io.InputStream;

public class DefaultEq {
    String name;
    public DefaultEq(String name){
        this.name = name;
    }
    public static void main(String[] args) {
        DefaultEq eq1 = new DefaultEq("張三");
        DefaultEq eq2 = new DefaultEq("張三");
        DefaultEq eq3 = eq1;

        //雖然倆對象外面看起來一樣,eq和==都不行
        //因為我們沒有改寫equals,它使用預設object的,也就是記憶體地址
        System.out.println(eq1.equals(eq2));
        System.out.println(eq1 == eq2);

        System.out.println("----");
        //1和3是同一個引用
        System.out.println(eq1.equals(eq3));
        System.out.println(eq1 == eq3);

        System.out.println("===");
        //以上是對象,再來看基本類型
        int i1 = 1;
        Integer i2 = 1;
        Integer i = new Integer(1);
        Integer j = new Integer(1);

        Integer k = new Integer(2);

        //只要是基本類型,不管值還是包裝成對象,都是直接比較大小
        System.out.println(i.equals(i1));  //比較的是值
        System.out.println(i==i1);  //拆箱 ,
        // 封裝對象i被拆箱,變為值比較,1==1成立
        //相當於 System.out.println(1==1);

        System.out.println(i.equals(j));  //
        System.out.println(i==j);   //  比較的是地址,這是倆對象

        System.out.println(i2 == i); // i2在常量池裡,i在堆里,地址不一樣

        System.out.println(i.equals(k));  //1和2,不解釋
    }
}


結論:

  • “==”比較的是什麼?

    用於基本數據(8種)類型(或包裝類型)相互比較,比較二者的值是否相等。

    用於引用數據(類、介面、數組)類型相互比較,比較二者地址是否相等。

  • equals比較的什麼?

    預設情況下,所有對象繼承Object,而Object的equals比較的就是記憶體地址

    所以預設情況下,這倆沒啥區別

2) 記憶體地址生成與比較

tips:既然沒區別,那我們看一下,記憶體地址到底是個啥玩意

目標:記憶體地址是如何來的?

Main.java

    public static void main(String[] args) {
        User  user1=new User("張三");
        User  user2=new User("張三");
    }

1、載入過程(回顧)

從java文件到jvm:

file

tips: 載入到方法區

這個階段只是User類的信息進入方法區,還沒有為兩個user來分配記憶體

2、分配記憶體空間

在main線程執行階段,指針碰撞(連續記憶體空間時),或者空閑列表(不連續空間)方式開闢一塊堆記憶體

每次new一個,開闢一塊,所以兩個new之間肯定不是相同地址,哪怕你new的都是同一個類型的class。

那麼它如何來保證記憶體地址不重覆的呢?(cas畫圖)

file

3、指向

在棧中創建兩個局部變數 user1,user2,指向堆里的記憶體

歸根到底,上面的==比較的是兩個對象的堆記憶體地址,也就是棧中局部變數表裡存儲的值。

public boolean equals(Object obj) {
    return (this == obj);//本類比較的是記憶體地址(引用)
}

3) 預設equals的問題

需求(or 目標):user1和user2,如果name一樣我們就認為是同一個人;如何處理?

tips:

面試最常問的問題

1、equals比較的什麼?

2、有沒有重寫過equals?

3、有沒有重寫過hashCode?

4、什麼情況下需要重寫equals()和hashCode()?

1、先拿User下手,看看它的預設行為com.eq.EqualsObjTest

    public static void main(String[] args) {
       //需求::user1和user2,在現實生活中是一個人;如何判定是一個人(相等)
        User user1 = new User("張三");
        User user2 = new User("張三");
        System.out.println("是否同一個人:"+user1.equals(user2));
        System.out.println("記憶體地址相等:"+String.valueOf(user1 == user2));//記憶體地址
        System.out.println("user1的hashCode為>>>>" + user1.hashCode());
        System.out.println("user2的hashCode為>>>>" + user2.hashCode());
    }

輸出如下

file

結論:

很顯然,預設的User繼承了Object的方法,而object,根據上面的源碼分析我們知道,equals就是記憶體地址。

而你兩次new User,不管name怎麼一致,記憶體分配,肯定不是同一個地址!

怎麼破?

2、同樣的場景,我們把用戶名從User換成單純的字元串試試com.eq.EqualsStrTest

 public static void main(String[] args) {
        String str1 = "張三";//常量池
        String str2 = new String("張三");//堆中
        String str3 = new String("張三");//堆中
        System.out.println("是否同一人:"+str1.equals(str2));//這個地方為什麼相等呢,重寫
        System.out.println("是否同一人:"+str2.equals(str3));//這個地方為什麼相等呢,重寫
        //如果相等,hashcode必須相等,重寫
        System.out.println("str1的hashCode為>>" + str1.hashCode());
        System.out.println("str2的hashCode為>>" + str2.hashCode());
    }
}

輸出如下

file

達到了我們的逾期,相同的name,被判定為同一個人,為什麼呢?往下看!

String的源碼分析

    /**
     * Compares this string to the specified object.  The result is {@code
     * true} if and only if the argument is not {@code null} and is a {@code
     * String} object that represents the same sequence of characters as this
     * object.
     *
     * @param  anObject
     *         The object to compare this {@code String} against
     *
     * @return  {@code true} if the given object represents a {@code String}
     *          equivalent to this string, {@code false} otherwise
     *
     * @see  #compareTo(String)
     * @see  #equalsIgnoreCase(String)
     */
    public boolean equals(Object anObject) {
      	//如果記憶體地址相等,那必須equal
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
          	//如果對象是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) {
                  	//從前往後,任意一個字元不匹配,直接返回false
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
              	//全部匹配結束,返回true
                return true;
            }
        }
        return false;
    }

結論:

String類型改寫了equals方法,沒有使用Object的預設實現

它不管你是不是同一個記憶體地址,只要倆字元串里的字元都匹配上,那麼equals就認為它是true

3、據此,我們參照String,來重寫User的equals和hashCodecom.eq.User2

    @Override
    public boolean equals(Object o) {
      	//註意這些額外的判斷類操作
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        User user = (User) o;
        //比較值
        return name != null ? name.equals(user.name) : user.name == null;
    }

    @Override
    public int hashCode() {
        //返回值的hashCode
        return name != null ? name.hashCode() : 0;
    }

換成User2 再來跑試試 (參考 com.eq.EqualsObjTest2)

file
目的達到!

4)hashCode與equals

為什麼說hashCode和equals是一對搭檔?他倆到底啥關係需要綁定到一塊?

看代碼說話:(com.eq.Contains)

package com.eq;

import java.util.HashSet;
import java.util.Set;

public class Contains {
    public static void main(String[] args) {
        User user1 = new User("張三");
        User user2 = new User("張三");
        Set set = new HashSet();
        set.add(user1);
        System.out.println(set.contains(user2));


        User2 user3 = new User2("張三");
        User2 user4 = new User2("張三");
        Set set2 = new HashSet();
        set2.add(user3);
        System.out.println(set2.contains(user4));
    }
}

結論:

hashCode是給java集合類的一些動作提供支撐,來判斷倆對象“是否是同一個”的標準

equals是給你編碼時判斷用的,所以,這倆必須保持一致的邏輯。

5)總結

1、特殊業務需求需要重寫,比如上面的

2、例如map,key放自定義對象也需要重寫

3、重寫equals後必須要重寫hashCode,要保持邏輯上的一致!

1.2.5 關於雙等(擴展)

equals被重寫後,雙等還留著幹啥用?

1)String的特殊性

tips:面試常問的問題

intern是做什麼的?

先來看一段代碼:(com.eq.Intern)

public class Intern {
    public static void main(String[] args) {
        String str1 = "張三";//常量池
        String str2 = new String("張三");//堆中

        //intern;記憶體地址是否相等(面試常問)
        System.out.println("str1與str2是否相等>>" +(str1==str2));  // false
        System.out.println("str1與str2是否相等>>" +(str1==str2.intern()));  // true

    }
}

file

版本聲明:(JDK1.8)

new String是在堆上創建字元串對象。
當調用 intern() 方法時,
JVM會將字元串添加(堆引用指向常量池)到常量池中

註意:

1、1.8版本只是將hello word在堆中的引用指向常量池,之前的版本是把hello word複製到常量池

2、堆(字元串常量值) 方法區(運行時常量池)不要搞反了

2)valueOf里的秘密

關於雙等號地址問題,除了String.intern() , 在基礎類型里,如Integer,Long等同樣有一個方法:valueOf需要註意

我們先來看一個小例子: 猜一猜結果?

package com.eq;

public class Valueof {
    public static void main(String[] args) {
        System.out.println( Integer.valueOf(127) == Integer.valueOf(127));
        System.out.println( Integer.valueOf(128) == Integer.valueOf(128));
    }
}

奇怪的結果……

源碼分析(以Integer為例子):

    /**
     * Returns an {@code Integer} instance representing the specified
     * {@code int} value.  If a new {@code Integer} instance is not
     * required, this method should generally be used in preference to
     * the constructor {@link #Integer(int)}, as this method is likely
     * to yield significantly better space and time performance by
     * caching frequently requested values.
     *
     * !在-128 到 127 之間會被cache,同一個地址下,超出後返回new對象!
     *
     * This method will always cache values in the range -128 to 127,
     * inclusive, and may cache other values outside of this range.
     *
     * @param  i an {@code int} value.
     * @return an {@code Integer} instance representing {@code i}.
     * @since  1.5
     */
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

本文由傳智教育博學谷 - 狂野架構師教研團隊發佈
如果本文對您有幫助,歡迎關註和點贊;如果您有任何建議也可留言評論或私信,您的支持是我堅持創作的動力
轉載請註明出處!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、緩存機制的原理 一個系統在面向用戶使用的時候,當用戶的數量不斷增多,那麼請求次數也會不斷增多,當請求次數增多的時候,就會造成請求壓力,而我們當前的所有數據查詢都是從資料庫MySQL中直接查詢的,那麼就可能會產生如下問題 ==頻繁訪問資料庫,資料庫訪問壓力大,系統性能下降,用戶體驗差== 解決問題 ...
  • 今天我們來講解leetcode案例分析,如何動態規劃的解題套路,態規劃的核心思想,以前經常會遇到動態規劃類型題目。 ...
  • SpringBoot 2.7.2 學習系列,本節內容快速體驗Spring Boot,帶大家瞭解它的基本使用、運行和打包。 Spring Boot 基於 Spring 框架,底層離不開 IoC、AoP 等核心思想。Spring 4.0 提供了基於 Java Config 的開發方式,Spring Bo ...
  • 一、問題復現 在實際的軟體系統開發過程中,隨著使用的用戶群體越來越多,表數據也會隨著時間的推移,單表的數據量會越來越大。 以訂單表為例,假如每天的訂單量在 4 萬左右,那麼一個月的訂單量就是 120 多萬,一年就是 1400 多萬,隨著年數的增加和單日下單量的增加,訂單表的數據量會越來越龐大,訂單數 ...
  • 1. 登錄表單配置 1.1 快速入門 理解了入門案例之後,接下來我們再來看一下登錄表單的詳細配置,首先創建一個新的Spring Boot項目,引入Web和Spring Security依賴,代碼如下: <dependency> <groupId>org.springframework.boot</g ...
  • Sentinel 是阿裡中間件團隊開源的,面向分散式服務架構的輕量級高可用流量控制組件,主要以流量為切入點,從流量控制、熔斷降級、系統負載保護等多個維度來幫助用戶保護服務的穩定性。 大家可能會問:Sentinel 和之前常用的熔斷降級庫 Netflix Hystrix 有什麼異同呢? 本文將從多個角 ...
  • numpy.linalg 模塊包含線性代數的函數。使用這個模塊,可以計算逆矩陣、求特征值、解線性方程組以及求解行列式等。一、計算逆矩陣 線性代數中,矩陣A與其逆矩陣A ^(-1)相乘後會得到一個單位矩陣I。該定義可以寫為A *A ^(-1) =1。numpy.linalg 模塊中的 inv 函數可以 ...
  • 多商戶商城系統,也稱為B2B2C(BBC)平臺電商模式多商家商城系統。可以快速幫助企業搭建類似拼多多/京東/天貓/淘寶的綜合商城。 多商戶商城系統支持商家入駐加盟,同時滿足平臺自營、旗艦店等多種經營方式。平臺可以通過收取商家入駐費,訂單交易服務費,提現手續費,簡訊通道費等多手段方式,實現整體盈利。 ...
一周排行
    -Advertisement-
    Play Games
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...
  • 目錄前言PostgreSql安裝測試額外Nuget安裝Person.cs模擬運行Navicate連postgresql解決方案Garnet為什麼要選擇Garnet而不是RedisRedis不再開源Windows版的Redis是由微軟維護的Windows Redis版本老舊,後續可能不再更新Garne ...
  • C#TMS系統代碼-聯表報表學習 領導被裁了之後很快就有人上任了,幾乎是無縫銜接,很難讓我不想到這早就決定好了。我的職責沒有任何變化。感受下來這個系統封裝程度很高,我只要會調用方法就行。這個系統交付之後不會有太多問題,更多應該是做小需求,有大的開發任務應該也是第二期的事,嗯?怎麼感覺我變成運維了?而 ...
  • 我在隨筆《EAV模型(實體-屬性-值)的設計和低代碼的處理方案(1)》中介紹了一些基本的EAV模型設計知識和基於Winform場景下低代碼(或者說無代碼)的一些實現思路,在本篇隨筆中,我們來分析一下這種針對通用業務,且只需定義就能構建業務模塊存儲和界面的解決方案,其中的數據查詢處理的操作。 ...
  • 對某個遠程伺服器啟用和設置NTP服務(Windows系統) 打開註冊表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer 將 Enabled 的值設置為 1,這將啟用NTP伺服器功 ...
  • title: Django信號與擴展:深入理解與實踐 date: 2024/5/15 22:40:52 updated: 2024/5/15 22:40:52 categories: 後端開發 tags: Django 信號 松耦合 觀察者 擴展 安全 性能 第一部分:Django信號基礎 Djan ...
  • 使用xadmin2遇到的問題&解決 環境配置: 使用的模塊版本: 關聯的包 Django 3.2.15 mysqlclient 2.2.4 xadmin 2.0.1 django-crispy-forms >= 1.6.0 django-import-export >= 0.5.1 django-r ...
  • 今天我打算整點兒不一樣的內容,通過之前學習的TransformerMap和LazyMap鏈,想搞點不一樣的,所以我關註了另外一條鏈DefaultedMap鏈,主要調用鏈為: 調用鏈詳細描述: ObjectInputStream.readObject() DefaultedMap.readObject ...
  • 後端應用級開發者該如何擁抱 AI GC?就是在這樣的一個大的浪潮下,我們的傳統的應用級開發者。我們該如何選擇職業或者是如何去快速轉型,跟上這樣的一個行業的一個浪潮? 0 AI金字塔模型 越往上它的整個難度就是職業機會也好,或者說是整個的這個運作也好,它的難度會越大,然後越往下機會就會越多,所以這是一 ...
  • @Autowired是Spring框架提供的註解,@Resource是Java EE 5規範提供的註解。 @Autowired預設按照類型自動裝配,而@Resource預設按照名稱自動裝配。 @Autowired支持@Qualifier註解來指定裝配哪一個具有相同類型的bean,而@Resourc... ...