【面經】被虐了之後,我翻爛了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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...