剖析Java中在Collection集合中使用contains和remove為什么要重寫equals
引言
在Collection集合中:
contains方法是判斷一個(gè)集合里面是否包含指定元素,如果有則返回true;
remove方法是從集合中刪除指定元素的單個(gè)實(shí)例;
這兩個(gè)方法看起很簡(jiǎn)單,用起來也很簡(jiǎn)單,同樣也非常常用;但是,它們到底是怎么匹配到相應(yīng)的元素呢?
源碼剖析
以ArrayList為例,我們分析一下ArrayList中的contains和remove的源碼;
先看看contains:
這里看到比較的對(duì)象是一個(gè)Object類,變量名為 o(就是是否包含 o ),并且調(diào)用了一個(gè)indexOf方法,接下來我們進(jìn)一步看看indexOf源碼:
可以看到,indexOf又進(jìn)一步調(diào)用了indexOfRange方法,我們還需要深入看看這個(gè)方法:
這里可以發(fā)現(xiàn),indexOfRange中 o 調(diào)用了equals方法(藍(lán)色部分)!
我們知道:equals方法是判斷兩個(gè)對(duì)象是否相等,但是默認(rèn)情況下比較的是對(duì)象的地址,如果想要比較對(duì)象的內(nèi)容就需要重寫equals方法;
那么這個(gè)contains調(diào)用了equals方法,所以,contains判斷一個(gè)集合中是否包含某個(gè)元素其實(shí)就是通過對(duì)象地址比較的了;
這并不是我們想要的結(jié)果,所以幾乎所有放在集合中的類型,都需要重寫equals方法!
為什么是幾乎所有?
因?yàn)檫€是有特例的:SUN公司已經(jīng)把String類和包裝類的equals方法重寫了,所以對(duì)于這兩種我們不需要重寫equals!
同樣看看remove方法:
同樣,remove方法也是通過equals方法比較元素然后移除的;
所以這里可以得出一個(gè)結(jié)論:
Collection集合中的remove方法和contains方法底層都會(huì)調(diào)用equals,所以只要放在集合中的類型,都要重寫equals方法;
因?yàn)閷?duì)對(duì)象的地址的比較沒有什么意義,我們實(shí)際上需要的是對(duì)象內(nèi)容間的比較;
實(shí)例測(cè)試
知道了結(jié)論,就來寫幾個(gè)代碼測(cè)試一下:
String類和包裝類的特殊情況
對(duì)于 String類型和包裝類,SUN公司重寫了equals方法,所以我們先測(cè)試一下這兩種情況:
import java.util.ArrayList; import java.util.Collection; // 結(jié)論:Collection接口中的remove方法和contains方法底層都會(huì)調(diào)用equals, // 所以存放在一個(gè)集合中的類型,要重寫它的equals方法 // (但是String和包裝類的equals方法已經(jīng)重寫過了,不用重寫) public class CollectionTest02 { public static void main(String[] args) { // 這里以ArrayList為例 Collection array1 = new ArrayList(); // String類: String s1 = "Hello"; // 將s1放入array1 array1.add(s1); // 定義一個(gè)s2也為 "Hello",那么調(diào)用contains是否會(huì)包含s2? String s2 = "Hello"; System.out.println("array1是否包含s2?" + array1.contains(s2)); // true // 因?yàn)閍rray1中放入的是s1,如果移除s2,s1會(huì)不會(huì)被移除呢? array1.remove(s2); System.out.println("移除s2后s1是否還在array1中?" + array1.contains(s1)); // false // 包裝類也同樣: Integer num1 = 1000; // 將num1放入array1 array1.add(num1); // 定義一個(gè)num2也為1000 Integer num2 = 1000; System.out.println("array1是否包含num2?" + array1.contains(num2)); // true // 移除num2觀察num1是否會(huì)被移除 array1.remove(num2); System.out.println("移除num2后num1是否還在array1中?" + array1.contains(num1)); // false } }
輸出結(jié)果:
array1是否包含s2?true
移除s2后s1是否還在array1中?false
array1是否包含num2?true
移除num2后num1是否還在array1中?false
自定義類型
這是equals重寫的情況,接下來我自定義一個(gè)類型,看看沒有重寫會(huì)發(fā)生什么;
import java.util.ArrayList; import java.util.Collection; public class CollectionTest03 { public static void main(String[] args) { // 還是以ArrayList為例 Collection array = new ArrayList(); // 創(chuàng)建一個(gè)User對(duì)象u1 User u1 = new User("張三"); // 將u1對(duì)象放入array中 array.add(u1); // 再創(chuàng)建一個(gè)User對(duì)象,使其內(nèi)容和u1相同都為"張三",那么調(diào)用contains是否會(huì)包含該對(duì)象呢? System.out.println("array中是否包含新創(chuàng)建的對(duì)象?" + array.contains(new User("張三"))); // false // 移除一個(gè)內(nèi)容為"張三"的新的對(duì)象,u1是否會(huì)被移除? array.remove(new User("張三")); System.out.println("移除后u1是否存在?" + array.contains(u1)); // true } } // 自己定義一個(gè)User類 class User { // 成員變量:姓名 private String name; // 默認(rèn)構(gòu)造 User() {} // 有參構(gòu)造:初始化姓名 User(String name) { this.name = name; } }
輸出結(jié)果:
array中是否包含新創(chuàng)建的對(duì)象?false
移除后u1是否存在?true
可以看到,我自定義的User方法里沒有重寫equals方法,所以當(dāng)調(diào)用contains和remove時(shí),雖然傳入的對(duì)象內(nèi)容和u1的對(duì)象內(nèi)容相同都為“張三”,但是實(shí)際上比較的卻是對(duì)象的地址;
接下來我重寫User的equals方法,看看結(jié)果如何;
import java.util.ArrayList; import java.util.Collection; public class CollectionTest03 { public static void main(String[] args) { // 還是以ArrayList為例 Collection array = new ArrayList(); // 創(chuàng)建一個(gè)User對(duì)象u1 User u1 = new User("張三"); // 將u1對(duì)象放入array中 array.add(u1); // 再創(chuàng)建一個(gè)User對(duì)象,使其內(nèi)容和u1相同都為"張三",那么調(diào)用contains是否會(huì)包含該對(duì)象呢? System.out.println("array中是否包含新創(chuàng)建的對(duì)象?" + array.contains(new User("張三"))); // true // 移除一個(gè)內(nèi)容為"張三"的新的對(duì)象,u1是否會(huì)被移除? array.remove(new User("張三")); System.out.println("移除后u1是否存在?" + array.contains(u1)); // false } } // 自己定義一個(gè)User類 class User { // 成員變量:姓名 private String name; // 默認(rèn)構(gòu)造 User() {} // 有參構(gòu)造:初始化姓名 User(String name) { this.name = name; } // 重寫equals方法 ,通過name進(jìn)行比較 public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof User)) return false; User user = (User) o; return name.equals(user.name); } }
輸出結(jié)果:
array中是否包含新創(chuàng)建的對(duì)象?true
移除后u1是否存在?false
我只是重寫了一個(gè)equals方法,其他地方都沒變,結(jié)果完全不同;
所以這就驗(yàn)證了之前的結(jié)論:contains 和 remove 底層實(shí)現(xiàn)都調(diào)用了equals方法;
總結(jié)
其實(shí)這篇文章就是分析一下contains 和 remove 底層實(shí)現(xiàn),主要想說的還是那一句話:
Collection集合中的remove方法和contains方法底層都會(huì)調(diào)用equals,所以只要放在集合中的類型,都要重寫equals方法;(String和包裝類除外)
希望各位在Java學(xué)習(xí)中養(yǎng)成好習(xí)慣,要時(shí)刻惦記著equals的重寫,不然如果做了一個(gè)項(xiàng)目你連錯(cuò)在哪里都不好找到;
到此這篇關(guān)于剖析在Collection集合中使用contains和remove為什么要重寫equals的文章就介紹到這了,更多相關(guān)Collection 重寫 equals內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springbootadmin與security沖突問題及解決
這篇文章主要介紹了Springbootadmin與security沖突問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08Mybatis-Plus中的@TableName 和 table-prefix使用
table-prefix 是一個(gè)全局配置,它會(huì)自動(dòng)在所有表名前添加指定的前綴,這個(gè)配置對(duì)于那些使用一致命名約定的數(shù)據(jù)庫(kù)表非常有用,這篇文章主要介紹了Mybatis-Plus中的@TableName 和 table-prefix使用,需要的朋友可以參考下2024-08-08Java編程中二維數(shù)組的初始化和基本操作實(shí)例
這篇文章主要介紹了Java編程中二維數(shù)組的初始化和基本操作實(shí)例,是Java入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-10-10intellij idea快速查看當(dāng)前類中的所有方法(推薦)
這篇文章主要介紹了intellij idea快速查看當(dāng)前類中的所有方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09springboot+quartz以持久化的方式實(shí)現(xiàn)定時(shí)任務(wù)的代碼
這篇文章主要介紹了springboot+quartz以持久化的方式實(shí)現(xiàn)定時(shí)任務(wù)的相關(guān)知識(shí),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07Java快速批量移動(dòng)文件的實(shí)現(xiàn)方法
這篇文章主要介紹了Java快速批量移動(dòng)文件的實(shí)現(xiàn)方法,需要的朋友可以參考下2014-03-03springBoot項(xiàng)目中使用@Value取值出現(xiàn)的問題及解決
這篇文章主要介紹了springBoot項(xiàng)目中使用@Value取值出現(xiàn)的問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07