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

