Java根據(jù)指定字段實(shí)現(xiàn)對(duì)對(duì)象進(jìn)行去重的五種方法
引入問(wèn)題
首先,我自定義了一個(gè)名為 Person 的 Java 類:
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Person 類中有兩個(gè)屬性:name 和 age 和一個(gè)全參構(gòu)造方法:
name是一個(gè)字符串類型的變量,用于表示人的姓名;age是一個(gè)整數(shù)類型的變量,用于表示人的年齡。- 構(gòu)造方法用于創(chuàng)建
Person類的對(duì)象。
并且重寫了三個(gè)方法:hashCode()、equals() 和 toString():
hashCode()方法返回對(duì)象的哈希碼,此處直接調(diào)用了父類Object的hashCode()方法。equals()方法用于比較對(duì)象是否相等,此處直接調(diào)用了父類Object的equals()方法。toString()方法返回一個(gè)描述該對(duì)象內(nèi)容的字符串,格式為"Person{name='姓名', age=年齡}"。
最終,我們需要根據(jù) Person 類的 name 字段對(duì)目標(biāo)集合進(jìn)行去重:
public static void main(String[] args) {
List<Person> persons = new ArrayList<>();
persons.add(new Person("Tom", 20));
persons.add(new Person("Jerry", 18));
persons.add(new Person("Tom", 22));
persons.add(new Person("Jim", 23));
persons.add(new Person("Tom", 22));
persons.forEach(System.out::println);
}
方法一:使用 HashSet 數(shù)據(jù)結(jié)構(gòu)
根據(jù) Java 對(duì)象某個(gè)字段進(jìn)行去重,可以使用 HashSet 數(shù)據(jù)結(jié)構(gòu)。HashSet 內(nèi)部實(shí)現(xiàn)了哈希表,能夠快速判斷元素是否已存在,從而實(shí)現(xiàn)去重。
Tips: HashSet 是如何實(shí)現(xiàn)元素去重的,或者說(shuō)如何判斷元素是否重復(fù)?
在 Java 中,HashSet 是一種基于哈希表實(shí)現(xiàn)的集合類,它內(nèi)部維護(hù)了一個(gè)存儲(chǔ)元素的哈希表。HashSet 通過(guò)元素的哈希碼(hashcode)來(lái)判斷元素是否重復(fù)的。
當(dāng)我們向 HashSet 中添加元素時(shí),HashSet 會(huì)首先計(jì)算該元素的哈希碼,并根據(jù)哈希碼將元素放入對(duì)應(yīng)的桶中。如果該桶中已經(jīng)有了相同哈希碼的元素,則會(huì)調(diào)用元素的 equals() 方法,比較元素是否相等。如果相等,則認(rèn)為該元素已經(jīng)存在于 HashSet 中,不進(jìn)行重復(fù)添加;否則將該元素添加到集合中。
如果我們通過(guò) HashSet 進(jìn)行去重,就需要正確地實(shí)現(xiàn) hashCode() 和 equals() 方法。hashCode() 方法應(yīng)該返回與元素屬性相關(guān)的哈希碼,而 equals() 方法應(yīng)該根據(jù)元素屬性判斷元素是否相等。只有這樣才能保證在 HashSet 中正確地去重和查找元素。
使用 HashSet 去重的具體步驟如下:
- 重寫對(duì)象的
equals和hashCode方法。在這兩個(gè)方法中,分別比較對(duì)象的指定字段,并返回相應(yīng)的哈希值。 - 創(chuàng)建一個(gè)
HashSet對(duì)象,并將所有要去重的對(duì)象添加到該HashSet中。 - 遍歷該
HashSet,處理去重后的結(jié)果。
重寫 Person 類的 equals 和 hashCode 方法,用于比較指定字段:
public class Person {
......
// 重寫 hashCode 方法
@Override
public int hashCode() {
// 哈希值只與 name 字段有關(guān)
return name.hashCode();
}
// 重寫 equals 方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
// 比較 name 字段
return name.equals(person.name);
}
}
在重寫的 equals() 方法中,首先使用 this == o 來(lái)判斷兩個(gè)對(duì)象是否為同一個(gè)對(duì)象(即內(nèi)存地址是否相同),如果是,則直接返回 true。如果不是同一個(gè)對(duì)象,則繼續(xù)比較其他屬性。
接著,使用 o == null 判斷傳入的參數(shù)是否為 null,如果是 null,則兩個(gè)對(duì)象肯定不相等,直接返回 false。然后使用 getClass() 方法來(lái)獲取傳入對(duì)象的類,判斷其是否與當(dāng)前對(duì)象的類相同,如果不同,則兩個(gè)對(duì)象肯定不相等,直接返回 false。
最后,將參數(shù)對(duì)象強(qiáng)制轉(zhuǎn)換成 Person 類型,并比較兩個(gè)對(duì)象的 name 屬性是否相等。如果相等,則認(rèn)為兩個(gè)對(duì)象相等,返回 true,否則返回 false。
同時(shí)重寫 hashCode() 方法,以確保兩個(gè)對(duì)象相等時(shí)它們的哈希碼也相等。
使用 HashSet 去重:
public static void main(String[] args) {
......
HashSet<Person> personHashSet = new HashSet<>(persons);
personHashSet.forEach(System.out::println);
}
去重結(jié)果為:
Person{name='Tom', age=20}
Person{name='Jerry', age=18}
Person{name='Jim', age=23}
方法二:使用 Java 8 的 Stream API 的 distinct() 去重
Java 8 增加的 Stream API 提供了 distinct() 方法去重。
Stream 流的 distinct() 方法是基于對(duì)象的 equals() 方法來(lái)判斷對(duì)象是否相等,因此我們需要重寫 Person 類的 equals() 方法:
public class Person {
......
// 重寫 equals 方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
// 比較 name 字段
return name.equals(person.name);
}
}
之后調(diào)用 stream() 方法將列表轉(zhuǎn)換為流,并且使用 distinct() 方法基于 name 字段進(jìn)行去重:
List<Person> collect = persons.stream()
.distinct()
.collect(Collectors.toList());
最后打印去重后的 Person 集合:
Person{name='Tom', age=20}
Person{name='Jerry', age=18}
Person{name='Jim', age=23}
方法三:使用 Map 數(shù)據(jù)結(jié)構(gòu)
Java 中的 Map 是一種用于存儲(chǔ)鍵值對(duì)的集合。我們可以利用 Map 中的鍵唯一的特性實(shí)現(xiàn)去重。
我們只需要遍歷 List 中的 Person 對(duì)象,將 name 作為 key,Person 對(duì)象作為 value 存入 Map 中,這樣就可以去除重復(fù)的 name 對(duì)應(yīng)的 Person 對(duì)象:
public static void main(String[] args) {
......
Map<String, Person> map = new HashMap<>();
for (Person person : persons) {
map.put(person.getName(), person);
}
}
去重結(jié)果如下:
Person{name='Tom', age=22}
Person{name='Jerry', age=18}
Person{name='Jim', age=23}
方法四:使用 Collectors.toMap() 方法
Collectors.toMap()是 Java 8 中的一個(gè)收集器(Collector),它可以將 Stream 中的元素收集到一個(gè) Map 中,其中每個(gè)元素都是一個(gè)鍵值對(duì)。該方法有多個(gè)重載形式:
toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)
將 Stream 中的元素轉(zhuǎn)換為鍵值對(duì),并存儲(chǔ)到一個(gè)Map中。其中,keyMapper用于指定如何從元素中提取鍵,valueMapper用于指定如何從元素中提取值。
如果存在重復(fù)的鍵,則會(huì)拋出IllegalStateException異常。
toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction)
與第一種形式類似,但當(dāng)存在重復(fù)的鍵時(shí),會(huì)使用mergeFunction函數(shù)來(lái)處理沖突。例如,可以使用mergeFunction來(lái)選擇較小或較大的值,或?qū)蓚€(gè)值合并成一個(gè)新值。
toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)
與第二種形式類似,但允許指定用于創(chuàng)建 Map 的具體實(shí)現(xiàn)類。
使用第二種重載形式將包含Person對(duì)象的List轉(zhuǎn)換為一個(gè)以Person對(duì)象的姓名作為鍵的Map:
public static void main(String[] args) {
......
Map<String, Person> collect = persons.stream()
.collect(Collectors.toMap(Person::getName, p -> p, (p1, p2) -> p1));
}
Person::getName:函數(shù)式接口Function類型的方法引用,用于將Person對(duì)象的姓名作為鍵。person -> person:Lambda 表達(dá)式,用于將Person對(duì)象本身作為值。(p1, p2) -> p1:Lambda 表達(dá)式,用于處理當(dāng)存在重復(fù)鍵時(shí)的情況。此處選擇保留第一個(gè)鍵對(duì)應(yīng)的值,而忽略第二個(gè)鍵對(duì)應(yīng)的值。
去重結(jié)果如下:
Person{name='Tom', age=20}
Person{name='Jerry', age=18}
Person{name='Jim', age=23}
方法五:使用 Collectors.collectingAndThen() 方法
Collectors.collectingAndThen()是 Java 8 中的一個(gè)收集器(Collector)方法,它允許在收集元素后應(yīng)用一個(gè)最終轉(zhuǎn)換函數(shù)。在使用collectingAndThen()時(shí),先通過(guò)一個(gè)初始的收集器將元素收集起來(lái),然后再應(yīng)用一個(gè)最終轉(zhuǎn)換函數(shù)對(duì)收集結(jié)果進(jìn)行處理。
以下是collectingAndThen()方法的常用重載形式:
collectingAndThen(Collector<T, A, R> downstream, Function<R, RR> finisher)
downstream:初始的收集器,用于將元素收集起來(lái)并生成一個(gè)中間結(jié)果。finisher:最終轉(zhuǎn)換函數(shù),用于對(duì)中間結(jié)果進(jìn)行處理,并返回最終結(jié)果。
使用collectingAndThen()方法實(shí)現(xiàn)去重并返回去重后的結(jié)果集:
public static void main(String[] args) {
......
ArrayList<Person> collect = persons.stream()
.collect(Collectors.collectingAndThen(
Collectors.toMap(Person::getName, person -> person, (p1, p2) -> p1),
map -> new ArrayList<>(map.values())
));
}
- 使用
Collectors.toMap()將persons流中的元素轉(zhuǎn)換為一個(gè)以name作為鍵的Map。 - 通過(guò)
map -> new ArrayList<>(map.values())將Map的值部分提取出來(lái),并使用ArrayList的構(gòu)造函數(shù)將其包裝為一個(gè)新的ArrayList<Person>對(duì)象。最終得到的ArrayList<Person>對(duì)象即為去重后的結(jié)果集。
輸出去重后的結(jié)果:
Person{name='Tom', age=20}
Person{name='Jerry', age=18}
Person{name='Jim', age=23}
以上就是Java根據(jù)指定字段實(shí)現(xiàn)對(duì)對(duì)象進(jìn)行去重的五種方法的詳細(xì)內(nèi)容,更多關(guān)于Java指定字段對(duì)對(duì)象去重的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Mybatis與Jpa的區(qū)別和性能對(duì)比總結(jié)
mybatis和jpa兩個(gè)持久層框架,從底層到用法都不同,但是實(shí)現(xiàn)的功能是一樣的,所以說(shuō)一直以來(lái)頗有爭(zhēng)議,所以下面這篇文章主要給大家介紹了關(guān)于Mybatis與Jpa的區(qū)別和性能對(duì)比的相關(guān)資料,需要的朋友可以參考下2021-06-06
Java實(shí)現(xiàn)復(fù)原IP地址的方法
這篇文章主要介紹了Java實(shí)現(xiàn)復(fù)原IP地址的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02
MyBatis3傳遞多個(gè)參數(shù)(Multiple Parameters)
這篇文章主要介紹了MyBatis3傳遞多個(gè)參數(shù),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
Java基于NIO實(shí)現(xiàn)群聊系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了Java基于NIO實(shí)現(xiàn)群聊系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
AsyncHttpClient ListenableFuture源碼流程解讀
這篇文章主要為大家介紹了AsyncHttpClient ListenableFuture源碼流程解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
Mybatis流式查詢并實(shí)現(xiàn)將結(jié)果分批寫入文件
這篇文章主要介紹了Mybatis流式查詢并實(shí)現(xiàn)將結(jié)果分批寫入文件方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08
java實(shí)戰(zhàn)小技巧之優(yōu)雅的實(shí)現(xiàn)字符串拼接
字符串拼接是我們?cè)贘ava代碼中比較經(jīng)常要做的事情,就是把多個(gè)字符串拼接到一起,這篇文章主要給大家介紹了關(guān)于java實(shí)戰(zhàn)小技巧之優(yōu)雅的實(shí)現(xiàn)字符串拼接的相關(guān)資料,需要的朋友可以參考下2021-08-08
MyBatis-Plus中AutoGenerator的使用案例
AutoGenerator是MyBatis-Plus的代碼生成器,通過(guò)?AutoGenerator?可以快速生成?Pojo、Mapper、?Mapper?XML、Service、Controller?等各個(gè)模塊的代碼,這篇文章主要介紹了MyBatis-Plus中AutoGenerator的詳細(xì)使用案例,需要的朋友可以參考下2023-05-05

