1. 程式人生 > 實用技巧 >List 集合去重的 3 種方

List 集合去重的 3 種方

問題由來

在實際開發的時候,我們經常會碰到這麼一個困難:一個集合容器裡面有很多重複的物件,裡面的物件沒有主鍵,但是根據業務的需求,實際上我們需要根據條件篩選出沒有重複的物件

比較暴力的方法,就是根據業務需求,通過兩層迴圈來進行判斷,沒有重複的元素就加入到新集合中,新集合中已經有的元素就跳過。

操作例子如下,建立一個實體物件PenBean,程式碼如下:

/**
*筆實體
*/
publicclassPenBean{

/**型別*/
privateStringtype;

/**顏色*/
privateStringcolor;

//...省略setter和getter

publicPenBean(Stringtype,Stringcolor){
this.type=type;
this.color=color;
}

@Override
publicStringtoString(){
return"PenBean{"+
"type='"+type+'\''+
",color='"+color+'\''+
'}';
}
}

測試 demo,如下:

publicstaticvoidmain(String[]args){
//新增資訊,PenBean中沒有主鍵
List<PenBean>penBeanList=newArrayList<PenBean>();
penBeanList.add(newPenBean("鉛筆","black"));
penBeanList.add(newPenBean("鉛筆","white"));
penBeanList.add(newPenBean("鉛筆","black"));
penBeanList.add(newPenBean("中性筆","white"));
penBeanList.add(newPenBean("中性筆","white"));

//新資料
List<PenBean>newPenBeanList=newArrayList<PenBean>();
//傳統重複判斷
for(PenBeanpenBean:penBeanList){
if(newPenBeanList.isEmpty()){
newPenBeanList.add(penBean);
}else{
booleanisSame=false;
for(PenBeannewPenBean:newPenBeanList){
//依靠type、color來判斷,是否有重複元素
//如果新集合包含元素,直接跳過
if(penBean.getType().equals(newPenBean.getType())&&penBean.getColor().equals(newPenBean.getColor())){
isSame=true;
break;
}
}
if(!isSame){
newPenBeanList.add(penBean);
}
}
}

//輸出結果
System.out.println("=========新資料======");
for(PenBeanpenBean:newPenBeanList){
System.out.println(penBean.toString());
}
}

輸出結果:

=========新資料======
PenBean{type='鉛筆',color='black'}
PenBean{type='鉛筆',color='white'}
PenBean{type='中性筆',color='white'}

一般處理陣列型別的物件時,可以通過這種方法來對陣列元素進行去重操作,以篩選出沒有包含重複元素的陣列。

那有沒有更加簡潔的寫法呢?

答案肯定是有的,List中的contains()方法就是!

1、利用list中contains方法去重

在使用contains()之前,必須要對PenBean類重寫equals()方法,為什麼要這麼做?等會會詳細解釋!

我們先在PenBean類中重寫equals()方法,內容如下:

@Override
publicbooleanequals(Objecto){
if(this==o)returntrue;
if(o==null||getClass()!=o.getClass())returnfalse;
PenBeanpenBean=(PenBean)o;
//當type、color內容都相等的時候,才返回true
returnObjects.equals(type,penBean.type)&&
Objects.equals(color,penBean.color);
}

修改測試 demo,如下:

publicstaticvoidmain(String[]args){
//新增資訊
List<PenBean>penBeanList=newArrayList<PenBean>();
penBeanList.add(newPenBean("鉛筆","black"));
penBeanList.add(newPenBean("鉛筆","white"));
penBeanList.add(newPenBean("鉛筆","black"));
penBeanList.add(newPenBean("中性筆","white"));
penBeanList.add(newPenBean("中性筆","white"));

//新資料
List<PenBean>newPenBeanList=newArrayList<PenBean>();
//使用contain判斷,是否有相同的元素
for(PenBeanpenBean:penBeanList){
if(!newPenBeanList.contains(penBean)){
newPenBeanList.add(penBean);
}
}

//輸出結果
System.out.println("=========新資料======");
for(PenBeanpenBean:newPenBeanList){
System.out.println(penBean.toString());
}
}

輸出結果如下:

=========新資料======
PenBean{type='鉛筆',color='black'}
PenBean{type='鉛筆',color='white'}
PenBean{type='中性筆',color='white'}

如果PenBean物件不重寫equals()contains()方法的都是false!新資料與源資料是一樣的,並不能達到我們想要除去重複元素的目的

那麼contains()是怎麼做到,判斷一個集合裡面有相同的元素呢?

我們開啟ArrayListcontains()方法,原始碼如下:

publicbooleancontains(Objecto){
returnindexOf(o)>=0;
}

找到indexOf(o)方法,繼續往下看,原始碼如下:

publicintindexOf(Objecto){
if(o==null){
for(inti=0;i<size;i++)
if(elementData[i]==null)
returni;
}else{
for(inti=0;i<size;i++)
//物件通過equals方法,判斷是否相同
if(o.equals(elementData[i]))
returni;
}
return-1;
}

此時,非常清晰了,如果傳入的物件是null,for迴圈判斷陣列中的元素是否有null,如果有就返回下標;如果傳入的物件不是null,通過物件的equals()方法,for迴圈判斷是否有相同的元素,如果有就返回下標!

如果是陣列返回的下標,肯定是大於0,否則返回-1!

這就是為什麼在List中使用contains()方法,物件需要重寫equals()方法的原因!

2、java 8中去重操作

當然,有些朋友可能會想到 JDK1.8 中的流式寫法,例如 jdk1.8 中的集合元素去重寫法如下:

publicstaticvoidmain(String[]args){
//新增資訊
List<PenBean>penBeanList=newArrayList<PenBean>();
penBeanList.add(newPenBean("鉛筆","black"));
penBeanList.add(newPenBean("鉛筆","white"));
penBeanList.add(newPenBean("鉛筆","black"));
penBeanList.add(newPenBean("中性筆","white"));
penBeanList.add(newPenBean("中性筆","white"));

//使用java8新特性stream進行List去重
List<PenBean>newPenBeanList=penBeanList.stream().distinct().collect(Collectors.toList());

//輸出結果
System.out.println("=========新資料======");
for(PenBeanpenBean:newPenBeanList){
System.out.println(penBean.toString());
}
}

利用 jdk1.8 中提供的Stream.distinct()列表去重,Stream.distinct()使用hashCode()equals()方法來獲取不同的元素,因此使用這種寫法,物件需要重寫hashCode()equals()方法!

PenBean物件重寫hashCode()方法,程式碼如下:

@Override
publicinthashCode(){
returnObjects.hash(type,color);
}

在執行測試demo,結果如下:

=========新資料======
PenBean{type='鉛筆',color='black'}
PenBean{type='鉛筆',color='white'}
PenBean{type='中性筆',color='white'}

即可實現集合元素的去重操作!

那為什麼當我們使用String型別的物件作為集合元素時,沒有重寫呢?

因為 java 中String原生類,已經重寫好了,原始碼如下:

publicfinalclassString
implementsjava.io.Serializable,Comparable<String>,CharSequence{

@Override
publicbooleanequals(ObjectanObject){
if(this==anObject){
returntrue;
}
if(anObjectinstanceofString){
StringanotherString=(String)anObject;
intn=value.length;
if(n==anotherString.value.length){
charv1[]=value;
charv2[]=anotherString.value;
inti=0;
while(n--!=0){
if(v1[i]!=v2[i])
returnfalse;
i++;
}
returntrue;
}
}
returnfalse;
}

@Override
publicinthashCode(){
inth=hash;
if(h==0&&value.length>0){
charval[]=value;

for(inti=0;i<value.length;i++){
h=31*h+val[i];
}
hash=h;
}
returnh;
}
}

3、HashSet去重操作

在上面的分享中,我們介紹了 List 的集合去重操作!其中網友還提到了HashSet可以實現元素的去重!

的確,HashSet集合天然支援元素不重複!

實踐程式碼如下!

還是先建立一個物件PenBean,同時重寫Object中的equals()hashCode()方法,如下:

/**
*筆實體
*/
publicclassPenBean{
/**型別*/
privateStringtype;
/**顏色*/
privateStringcolor;
//...省略setter和getter
publicPenBean(Stringtype,Stringcolor){
this.type=type;
this.color=color;
}
@Override
publicStringtoString(){
return"PenBean{"+
"type='"+type+'\''+
",color='"+color+'\''+
'}';
}

@Override
publicbooleanequals(Objecto){
if(this==o)returntrue;
if(o==null||getClass()!=o.getClass())returnfalse;
PenBeanpenBean=(PenBean)o;
//當type、color內容都相等的時候,才返回true
returnObjects.equals(type,penBean.type)&&
Objects.equals(color,penBean.color);
}

@Override
publicinthashCode(){
returnObjects.hash(type,color);
}

}

建立測試 demo,如下:

publicstaticvoidmain(String[]args){
//新增資訊
List<PenBean>penBeanList=newArrayList<PenBean>();
penBeanList.add(newPenBean("鉛筆","black"));
penBeanList.add(newPenBean("鉛筆","white"));
penBeanList.add(newPenBean("鉛筆","black"));
penBeanList.add(newPenBean("中性筆","white"));
penBeanList.add(newPenBean("中性筆","white"));

//新資料
List<PenBean>newPenBeanList=newArrayList<PenBean>();
//set去重
HashSet<PenBean>set=newHashSet<>(penBeanList);
newPenBeanList.addAll(set);

//輸出結果
System.out.println("=========新資料======");
for(PenBeanpenBean:newPenBeanList){
System.out.println(penBean.toString());
}
}

輸出結果如下:

=========新資料======
PenBean{type='鉛筆',color='white'}
PenBean{type='鉛筆',color='black'}
PenBean{type='中性筆',color='white'}

很明細,返回的新集合沒有重複元素!

HashSet是怎麼做的的呢?

開啟HashSet的原始碼,檢視我們傳入的構造方法如下:

publicHashSet(Collection<?extendsE>c){
map=newHashMap<>(Math.max((int)(c.size()/.75f)+1,16));
addAll(c);
}

很顯然,首先建立了一個HashMap物件,然後呼叫addAll()方法,繼續往下看這個方法!

publicbooleanaddAll(Collection<?extendsE>c){
booleanmodified=false;
for(Ee:c)
if(add(e))
modified=true;
returnmodified;
}

首先遍歷List中的元素,然後呼叫add()方法,這個方法,原始碼如下:

publicbooleanadd(Ee){
returnmap.put(e,PRESENT)==null;
}

其實,就是向HashMap物件中插入元素,其中PRESENT是一個new Object()常量!

privatestaticfinalObjectPRESENT=newObject();

到這裡就基本很清楚了,向HashSet中新增元素,其實等同於

Map<Object,Object>map=newHashMap<Object,Object>();
map.put(e,newObject);//e表示要插入的元素

其中插入的元素e,就是HashMap中的key

我們知道HashMap,是通過equals()hashCode()來判斷插入的key是否為同一個key,因此,當我們對PenBean物件進行重寫equals()hashCode()時,保證判斷是同一個key時,就可以達到元素去重的目的!

最後,對已經去重的集合HashSet,再通過ArrayList中的addAll()方法進行包裝,即可得到我們想要的不包含重複元素的資料

最後

你還知道哪些集合去重的方法,歡迎評論區留言補充~

歡迎關注我的公眾號,每天分享關於Java學習資料和Java技術文章