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()
是怎麼做到,判斷一個集合裡面有相同的元素呢?
我們開啟ArrayList
中contains()
方法,原始碼如下:
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技術文章