1. 程式人生 > >【Java】集合(List、Set、Map)遍歷、刪除、比較元素時的小陷阱

【Java】集合(List、Set、Map)遍歷、刪除、比較元素時的小陷阱

主要說明List,其餘兩個都一樣

一、漏網之魚-for迴圈遞增下標方式遍歷集合,並刪除元素

如果你用for迴圈遞增下標方式遍歷集合,在遍歷過程中刪除元素,你可能會遺漏了某些元素。說那麼說可能也說不清楚,看以下示例:

import java.util.ArrayList;
import java.util.List;

public class ListTest_Unwork {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add(
"1"); list.add("2"); list.add("3"); list.add("4"); list.add("5"); System.out.println("Original list : " + list); String temp = null; for (int i = 0; i < list.size(); i++) { temp = list.get(i);
            
            System.out.println(
"Check for " + temp); if ("3".equals(temp)) { list.remove(temp); } } System.out.println("Removed list : " + list); } }

日誌列印:

Original list : [1, 2, 3, 4, 5]
Check for 1
Check for 2
Check for 3
Check for 5
Removed  list : [1, 2, 4, 5]

如日誌所見,其中值為4的元素並未經過判斷,漏網之魚。 

解決方法為以下兩個(但一般不建議我們在遍歷中用不是遍歷本身的函式刪除元素,見關於“ConcurrentModificationException”的內容):

1、對於此情況,我一般都從後面開始遍歷,以避免問題:

import java.util.ArrayList;
import java.util.List;

public class ListTest_Work {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");
        System.out.println("Original list : " + list);
        System.out.println();

        String temp = null;
        for (int i = list.size() - 1; i >= 0; i--) {
            temp = list.get(i);
            
            System.out.println("Check for " + temp);
            if ("3".equals(temp)) {
                list.remove(temp);
            }
        }
        System.out.println("Removed  list : " + list);
    }

}

2、直接從新建立一個集合,重新擺放,但消耗記憶體慎用

import java.util.ArrayList;
import java.util.List;

public class ListTest_Work2 {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");
        System.out.println("Original list : " + list);
        System.out.println();

        List<String> tempList = new ArrayList<String>();
        for (String temp : list) {
            System.out.println("Check for " + temp);
            if (!"3".equals(temp)) {
                tempList.add(temp);
            }
        }
        System.out.println("Removed  list : " + tempList);
    }

}

二、ConcurrentModificationException異常-Iterator遍歷集合過程中用其他手段(或其他執行緒)操作元素

ConcurrentModificationException是Java集合的一個快速報錯(fail-fast)機制,防止多個執行緒同時修改同一個集合的元素。在用Iterator遍歷集合時,如果你用其他手段非Iterator自身手段)操作集合元素,就會報ConcurrentModificationException。

不信?用Iterator方式或簡寫的for(Object o : list) {}方式,遍歷集合,修改元素時會報異常:

import java.util.ArrayList;
import java.util.List;

public class ListTest2_Unwork {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");
        System.out.println("Original list : " + list);
        System.out.println();

        for (String temp : list) {
            System.out.println("Check for " + temp);
            if ("3".equals(temp)) {
                list.remove(temp);
            }
        }
        System.out.println("Removed  list : " + list);
    }

}

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class ListTest3_Unwork {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");
        System.out.println("Original list : " + list);
        System.out.println();

        Iterator<String> i = list.iterator();
        String temp = null;
        while (i.hasNext()) {
            temp = i.next();
            System.out.println("Check for " + temp);
            if ("3".equals(temp)) {
                list.remove(temp);
            }
        }
        System.out.println("Removed  list : " + list);
    }

}

 日誌:

Original list : [1, 2, 3, 4, 5]

Check for 1
Check for 2
Check for 3
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
    at java.util.ArrayList$Itr.next(ArrayList.java:831)
    at ListTest3_Unwork.main(ListTest3_Unwork.java:20)

在刪除元素“3”時,會報異常。 

對於此情況,需要用iterator的remove方法替代,結果是妥妥的:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class ListTest3_Work {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");
        System.out.println("Original list : " + list);
        System.out.println();

        Iterator<String> i = list.iterator();
        String temp = null;
        while (i.hasNext()) {
            temp = i.next();
            System.out.println("Check for " + temp);
            if ("3".equals(temp)) {
                i.remove();
            }
        }
        System.out.println("Removed  list : " + list);
    }

}

延伸個小問題,為什麼for(Object o : list) {}方式遍歷集合,現象和Iterator方式一樣,都會報錯呢?

答:這是因為Java的糖語法,“for(Object o : list) {}方式”只是Java語言用“易用性糖衣”吸引你的手段,本質上,它也是Iterator。不信,你寫下下面這段程式,反編譯看看就清楚了:

import java.util.ArrayList;
import java.util.List;

public class ForTester {
    
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("a");
        
        for (String s : list) {
            list.remove(s);
            System.out.println(s);
        }
    }
}

反編譯

import java.util.ArrayList;
import java.util.Iterator;

public class ForTester {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add("a");
        Iterator arg2 = list.iterator();

        while (arg2.hasNext()) {
            String s = (String) arg2.next();
            list.remove(s);
            System.out.println(s);
        }
    }
}

三、Set集合操作陷阱iew plain co

  1. package com.sort;  
  2. import java.util.HashSet;  
  3. import java.util.Iterator;  
  4. import java.util.Set;  
  5. /** 
  6.  * 一個不包含重複元素的 collection。更確切地講,set 不包含滿足 e1.equals(e2) 的元素對 e1 和 e2
  7.  */
  8. publicclass SetTest2 {  
  9.     publicstaticvoid main(String[] args) {  
  10.         Set<String> set = new HashSet<String>();  
  11.         set.add("a");  
  12.         set.add("b");  
  13.         set.add("c");  
  14.         set.add("d");  
  15.         set.add("e");  
  16.         /** 
  17.          * 遍歷方法一,迭代遍歷 
  18.          */
  19.         for(Iterator<String> iterator = set.iterator();iterator.hasNext();){  
  20.             System.out.print(iterator.next()+" ");  
  21.         }  
  22.         System.out.println();  
  23.         System.out.println("********************");  
  24.         /** 
  25.          * for增強迴圈遍歷 
  26.          */
  27.         for(String value : set){  
  28.             System.out.print(value+" ");  
  29.         }  
  30.     }  
  31. }  

當然Set集合也可以使用for迴圈了。

注意:這裡Set集合中放入的是String型別,假如我們放入一個自己定義的類例項的時候,比如Person類例項,這時候我們要自己重寫hashcode和equal方法,因為當使用HashSet時,會先比較物件的hashCode(),如果儲存在集合中的物件的hash code值是否與增加的物件的hash code值一致;如果不一致,直接加進去;如果一致,再進行equals方法的比較,equals方法如果返回true,表示物件已經加進去了,就不會再增加新的物件,否則加進去。

下面分析一下Set集合的另外一個重要實現類TreeSet
TreeSet使用元素的自然順序對元素進行排序,或者根據建立 set 時提供的 Comparator 進行排序,具體取決於使用的構造方法。 
通俗一點講,就是可以按照排序後的列表顯示,也可以按照指定的規則排序
view plain cop

  1. Set<String> set = new TreeSet<String>();  
  2.         set.add("f");  
  3.         set.add("a");  
  4.         set.add("b");  
  5.         set.add("c");  
  6.         set.add("d");  
  7.         set.add("e");  
  8.         System.out.println(set);  

輸出:[a, b, c, d, e, f]
按照排序後輸出。那麼如果我們想讓他倒序輸出呢?當然方法很多。這裡我採用指定一個規則讓他倒序輸出

  1. import java.util.Comparator;  
  2. 相關推薦

    Java集合ListSetMap刪除比較元素陷阱

    主要說明List,其餘兩個都一樣 一、漏網之魚-for迴圈遞增下標方式遍歷集合,並刪除元素 如果你用for迴圈遞增下標方式遍歷集合,在遍歷過程中刪除元素,你可能會遺漏了某些元素。說那麼說可能也說不清楚,看以下示例: import ja

    Spring中集合ListSetMap的配置和簡單使用

    1、首先寫一個實體類 package com.listtest.test; import java.util.List; import java.util.Map; import java.util.Set; public class Collect {

    Java集合整理List and Set and Map

    集合的由來 陣列長度是固定,當新增的元素超過了陣列的長度時需要對陣列重新定義,太麻煩,java內部給我們提供了集合類,能儲存任意物件,長度是可以改變的,隨著元素的增加而增加,隨著元素的減少而減少 陣列和集合的區別 區別1 : 陣列既可以儲存基本資

    Listset以及map方式的整理

      @Test public void foreachSet() { Set setObj = new HashSet(); setObj.add("dsf"); setObj.add(1); setObj.add(5); //first

    Java中的集合ListSet

    Java容器類主要是為了“儲存物件”,並將其劃分為兩個不同的概念:Collection,獨立元素的集合,這些元素都服從一條或多條規則,如List必須按照插入順序儲存元素,Set不能有重複元素,Queue按照排隊規則來確定物件的順序。Map形成一組“鍵值對”物件,允許你使用鍵

    Java集合系列18Arrays和Collections工具類

    1、Arrays類 Arrays類常用方法概述: 本類所有方法都是靜態的,本類方法是針對陣列的操作。 //部分Arrays的靜態方法(JDK1.8) static <T> List<T> asList(T... a)

    Java 集合系列01總體框架

    Java集合是Java提供的工具包,包含了常用的資料結構:集合、連結串列、佇列、棧、陣列、對映等。 Java集合工具包的位置是Java.util.*。 Java集合主要可以劃分為四個部分:List列表、Set集合、Map對映、工具類(Iterator迭代器、

    java簡介

    編碼 http ... 設計 適合 不能 高度 代碼格式 操作系統 應用:web後端開發、android-app開發、大數據應用開發 學習:java會過時,但程序設計的思想不會過時 特點:1、面向對象,跨平臺,語法比c++簡單     2、以字節碼的形式運行在虛擬機上   

    JavaDateUtil2

    繼承 ava sim pla bool private throw ons tar import java.sql.Timestamp; import java.text.ParseException; import java.text.SimpleDateFormat;

    java集合學習——Map 之 LinkedHashMap

    前言 jdk 版本 jdk1.8.0_161 UML結構圖 LinkedHashMap:Map 介面的 雜湊表 和 連結列表的 實現。 相對於 HashMap 的特性是:有序性(插入元素的順序有序),因為內部使用了 雙向連結串列實現。 原始碼 建構函式 主要

    javajson和list互相轉化工具類

    import java.util.List; import net.sf.json.JSONArray; public class JsonListUtil { /** * Li

    BZOJ1443遊戲二分圖匹配,博弈論

    () ans evel getchar mes 最大匹配 開始 就會 明顯 【BZOJ1443】遊戲(二分圖匹配,博弈論) 題面 BZOJ 題解 很明顯的二分圖博弈問題。 發現每次移動一定是從一個黑點到達一個白點,或者反過來。 所以可以對於棋盤進行染色然後連邊。 考慮一下必

    BZOJ3712Fiolki並查集重構樹

    bzoj3 long std 不同 合並 ++i 卡爾 優先級 href 【BZOJ3712】Fiolki(並查集重構樹) 題面 BZOJ 題解 很神仙的題目。 我們發現所有的合並關系構成了一棵樹。 那麽兩種不同的東西如果產生反應,一定在兩個聯通塊恰好聯通的時候反應。 那麽

    BZOJ4455小星星動態規劃,容斥

    之間 lld algorithm std 還需要 tchar 一次 lin 還需 【BZOJ4455】小星星(動態規劃,容斥) 題面 BZOJ 洛谷 Uoj 題解 題意說簡單點就是給定一張\(n\)個點的圖和一棵\(n\)個點的樹,現在要讓圖和樹之間的點一一對應,並且如果樹

    HDU2604Queuing矩陣快速冪+遞推

    題目連結 Queuing Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(

    POJ3169Layout差分約束系統+SPFA

    題目連結 Layout Time Limit: 1000MS Memory Limit: 65536K Total Submissions:14919 Accepted: 7183 Description Like everyone

    #290. ZJOI2017仙人掌數數+仙人掌+樹形dp

    傳送門 模擬賽的時候打了個表發現為一條鏈的時候答案是\(2^{n-2}\)竟然順便過了第一個點 然後之後訂正的時候強聯通分量打錯了調了一個上午 首先不難發現我們可以去掉所有在環上的邊,那麼就變成了一個森林,不同的樹之間不可能有連邊,那麼只要所有樹的答案乘起來就好了,只要在每一棵樹內部樹形\(dp\)即可

    BZOJ2330SDOI2012糖果差分約束,SPFA

    【BZOJ2330】【SDOI2012】糖果 題面 題目描述 幼兒園裡有N個小朋友,lxhgww老師現在想要給這些小朋友們分配糖果,要求每個小朋友都要分到糖果。但是小朋友們也有嫉妒心,總是會提出一些要求,比如小明不希望小紅分到的糖果比他的多,於是在

    HDU4652Dice數學期望,動態規劃

    題面 Vjudge 有一個m面骰子 詢問,連續出現n個相同的時候停止的期望 連續出現n個不同的時候停止的期望 題解 考慮兩種分開詢問來算。 第一種: 設f[i]表示已經有連續的i個相

    OpenGLSOIL簡易的opengl影象庫

    一、簡介 SOIL是簡易OpenGL影象庫(Simple OpenGL Image Library)的縮寫,它支援大多數流行的影象格式,並且使用簡單。可從官網下載其原始碼:http://www.lonesock.net/soil.html 二、配置 從他們的主頁可以