java中List集合刪除元素

List刪除所有指定元素

環(huán)境:jdk8

1.概要

java中List使用List.remove()直接刪除指定元素,然而高效刪除元素是很難, 在本文章中介紹多種

方法,討論其中優(yōu)點(diǎn)和缺點(diǎn),為了可讀性,我創(chuàng)建list(int…) 方法在測(cè)試類中,返回ArrayList

2.使用while循環(huán)

知道如何刪除一個(gè)元素,然后循環(huán)刪除肌蜻,看下簡(jiǎn)單例子

void removeAll(List<Integer> list, int element) {
    while (list.contains(element)) {
        list.remove(element);
    }
}

然而執(zhí)行下面會(huì)報(bào)錯(cuò)

// given
List<Integer> list = list(1, 2, 3);
int valueToRemove = 1;
 
// when
assertThatThrownBy(() -> removeAll(list, valueToRemove))
  .isInstanceOf(IndexOutOfBoundsException.class);

造成這個(gè)原因在第一個(gè)代碼塊3行互墓,調(diào)用List.remove(int),該參數(shù)被當(dāng)成list索引index,不是刪除元素

這個(gè)測(cè)試用例調(diào)用list.remove(1) ,但是刪除元素索引是0,調(diào)用List.remove() 改變所有元素在刪除元素之后

在這個(gè)場(chǎng)景我們刪除所有元素,除了第一條記錄,為什么僅僅只有第一條剩下呢,1代表索引是非法,因此最后會(huì)報(bào)錯(cuò)

注意,這個(gè)問題原因是調(diào)用List.remove() 參數(shù)是基本類型 short, char 或者int,因此編譯器第一次認(rèn)為調(diào)用匹配重載方法

可以用傳入Integer類型正確執(zhí)行

void removeAll(List<Integer> list, Integer element) {
    while (list.contains(element)) {
        list.remove(element);
    }
}

現(xiàn)在下面可以正確執(zhí)行難

// given
List<Integer> list = list(1, 2, 3);
int valueToRemove = 1;
 
// when
removeAll(list, valueToRemove);
 
// then
assertThat(list).isEqualTo(list(2, 3));

List.contains()List.remove() 都必須找到第一個(gè)出現(xiàn)元素,這個(gè)代碼引起沒必要遍歷

我們可以做到更好如果我們保存元素第一次出現(xiàn)索引

void removeAll(List<Integer> list, Integer element) {
    int index;
    while ((index = list.indexOf(element)) >= 0) {
        list.remove(index);
    }
}

以下代碼可以通過

 List<Integer> list = list(1,2,3);
int valueToRemove = 1;
// when
removeAll(list, valueToRemove);
assertThat(list).isEqualTo(list(2, 3));

上面情況代碼非常整潔和簡(jiǎn)潔,但是仍然性能很差,因?yàn)槲覀儾荒芨欉@個(gè)循環(huán)過程,List.remove()* 必須找到第一個(gè)list中元素然后刪除,當(dāng)使用ArrayList 蒋搜,元素改變引起許多引用拷貝篡撵,甚至重新分配內(nèi)存幾次

3.刪除元素直到改變?cè)瓉?em>list

List.remove(E element) 有一個(gè)特色我們還沒提及到,方法返回布爾值true,List 改變由于包含該元素并刪除 操作

注意點(diǎn)豆挽,List.remove(int index) 返回void育谬,因?yàn)楦鶕?jù)索引刪除是有效,List 總會(huì)刪除元素帮哈,否則會(huì)拋出異

IndexOutOfBoundsException

執(zhí)行刪除直到List 改變

void removeAll(List<Integer> list, int element) {
    while (list.remove(element));
}

結(jié)果如下

  // given
  List<Integer> list = list(1, 1, 2, 3);
  int valueToRemove = 1;

  // when
  removeAll(list, valueToRemove);

  // then
  assertThat(list).isEqualTo(list(2, 3));

上面代碼遇到之前同樣問題

3.使用for循環(huán)

我們可以管理遍歷過程通過for循環(huán)并且如果匹配元素直接刪除

void removeAll(List<Integer> list, int element) {
    for (int i = 0; i < list.size(); i++) {
        if (Objects.equals(element, list.get(i))) {
            list.remove(i);
        }
    }
}

結(jié)果如下:

// given
List<Integer> list = list(1, 2, 3);
int valueToRemove = 1;
 
// when
removeAll(list, valueToRemove);
 
// then
assertThat(list).isEqualTo(list(2, 3));

然而膛檀,如果不同輸入,得到錯(cuò)誤結(jié)果輸出:

// given
List<Integer> list = list(1, 1, 2, 3);
int valueToRemove = 1;
 
// when
removeAll(list, valueToRemove);
 
// then
assertThat(list).isEqualTo(list(1, 2, 3));

一步一步分析代碼:

  • i = 0
    • 元素和list.get(i)都是等于1在第3行代碼,因此java進(jìn)入if語句
    • 刪除元素索引0
    • list包含1宿刮,2和3
  • i = 1
    • list.get(i) 返回2因?yàn)閘ist刪除一個(gè)元素互站,因此改變所有元素位置

現(xiàn)在面臨問題當(dāng)有兩個(gè)相鄰值,我們都想刪除僵缺,解決這個(gè)問題胡桃,我們?cè)黾友h(huán)變量

當(dāng)刪除元素變量要減一

void removeAll(List<Integer> list, int element) {
    for (int i = 0; i < list.size(); i++) {
        if (Objects.equals(element, list.get(i))) {
            list.remove(i);
            i--;
        }
    }
}

當(dāng)我們不刪除變量增加1

void removeAll(List<Integer> list, int element) {
    for (int i = 0; i < list.size();) {
        if (Objects.equals(element, list.get(i))) {
            list.remove(i);
        } else {
            i++;
        }
    }
}

注意,在這之后磕潮,移除i++語句第2行

結(jié)果如下:

// given
List<Integer> list = list(1, 1, 2, 3);
int valueToRemove = 1;
 
// when
removeAll(list, valueToRemove);
 
// then
assertThat(list).isEqualTo(list(2, 3));

這個(gè)實(shí)現(xiàn)好像是對(duì)第一眼看上去翠胰,這個(gè)方法仍然有很嚴(yán)重性能問題:

  • 刪除元素改變之后所有元素
  • 索引訪問元素LinkedList 意味遍歷通過元素一個(gè)接一個(gè)知道找到該元素

4.使用for-each循環(huán)

從java5之后可以用for-each循環(huán)迭代通過list,下面使用迭代刪除元素:

void removeAll(List<Integer> list, int element) {
    for (Integer number : list) {
        if (Objects.equals(number, element)) {
            list.remove(number);
        }
    }
}

注意:使用Integer作為循環(huán)類型自脯,因此不會(huì)得到NullPointerException ,同時(shí)這個(gè)方法調(diào)用 List.remove(E element) 是我們期望調(diào)用方法之景,不是索引,

代碼很簡(jiǎn)潔膏潮,不幸是代碼報(bào)錯(cuò):

// given
List<Integer> list = list(1, 1, 2, 3);
int valueToRemove = 1;
 
// when
assertThatThrownBy(() -> removeWithForEachLoop(list, valueToRemove))
  .isInstanceOf(ConcurrentModificationException.class);

for-each循環(huán)使用迭代器遍歷元素锻狗,當(dāng)修改List 迭代器得到不一致狀態(tài),因此拋出常ConcurrentModificationException 焕参,從上面代碼得出結(jié)論:我們不能修改List轻纪,當(dāng)for-each訪問元素時(shí)候。

5.使用迭代器

使用迭代器遍歷和修改List

void removeAll(List<Integer> list, int element) {
    for (Iterator<Integer> i = list.iterator(); i.hasNext();) {
        Integer number = i.next();
        if (Objects.equals(number, element)) {
            i.remove();
        }
    }
}

這中方式叠纷,迭代器可以跟蹤List狀態(tài)(因?yàn)檫@個(gè)可以修改List),下面結(jié)果可以正常通過:

// given
List<Integer> list = list(1, 1, 2, 3);
int valueToRemove = 1;
 
// when
removeAll(list, valueToRemove);
 
// then
assertThat(list).isEqualTo(list(2, 3));

因?yàn)槊總€(gè)List類提供自己迭代器實(shí)現(xiàn)刻帚,我們可以安全假定,迭代器實(shí)現(xiàn)元素迭代和刪除最高效涩嚣。然而使用Arraylist 仍然要移動(dòng)很多元素(可以數(shù)組重新分配內(nèi)存)崇众,同時(shí)上面代碼有點(diǎn)難度這個(gè)不是標(biāo)準(zhǔn)for循環(huán)對(duì)于大多數(shù)開發(fā)來說不熟悉。

6.搜集

到目前為止航厚,刪除元素都會(huì)修改原List 顷歌,我們不必要這樣,可以創(chuàng)建新List 和搜集元素:

List<Integer> removeAll(List<Integer> list, int element) {
    List<Integer> remainingElements = new ArrayList<>();
    for (Integer number : list) {
        if (!Objects.equals(number, element)) {
            remainingElements.add(number);
        }
    }
    return remainingElements;
}

方法結(jié)果返回新的List 幔睬,方法必須返回list 衙吩,因此我們必須使用方法按照下面:

// given
List<Integer> list = list(1, 1, 2, 3);
int valueToRemove = 1;
 
// when
List<Integer> result = removeAll(list, valueToRemove);
 
// then
assertThat(result).isEqualTo(list(2, 3));

注意,現(xiàn)在使用for-each循環(huán)不能修改List 溪窒,我們現(xiàn)在通過它迭代元素,因?yàn)闆]用任何刪除,這里沒有必要移動(dòng)元素冯勉,因此這個(gè)實(shí)現(xiàn)性能也很好當(dāng)我們使用ArrayList

這實(shí)現(xiàn)比之前一些方式有些不同:

  • 它不會(huì)修改原List 但是返回新List
  • 這個(gè)方法決定返回List的實(shí)現(xiàn)澈蚌,它可以是不同于原List

同時(shí)我們修改我們實(shí)現(xiàn)得到以前方法獲得List,清除原LIst和增加搜集元素到原List

void removeAll(List<Integer> list, int element) {
    List<Integer> remainingElements = new ArrayList<>();
    for (Integer number : list) {
        if (!Objects.equals(number, element)) {
            remainingElements.add(number);
        }
    }
 
    list.clear();
    list.addAll(remainingElements);
}

和之前一樣

// given
List<Integer> list = list(1, 1, 2, 3);
int valueToRemove = 1;
 
// when
removeAll(list, valueToRemove);
 
// then
assertThat(list).isEqualTo(list(2, 3));

不需要修改原List灼狰,不必要按照位置訪問或者改變宛瞄,同時(shí),這里有兩個(gè)Array分配,當(dāng)調(diào)用List.clear() and List.addAll().

7.使用stream api

java 8 介紹lambda表達(dá)式和stream api份汗,有這寫強(qiáng)大特色盈电,我們可以解決我們問題并且用很簡(jiǎn)潔代碼

List<Integer> removeAll(List<Integer> list, int element) {
    return list.stream()
      .filter(e -> !Objects.equals(e, element))
      .collect(Collectors.toList());
}

這個(gè)方法作用和上一部分一樣,當(dāng)我們收集保存元素杯活,然后把這些結(jié)果增加原List 匆帚,有相同特征,

我們應(yīng)該返回結(jié)果:

// given
List<Integer> list = list(1, 1, 2, 3);
int valueToRemove = 1;
 
// when
List<Integer> result = removeAll(list, valueToRemove);
 
// then
assertThat(result).isEqualTo(list(2, 3));

8. 使用removeIf

有l(wèi)ambdas和函數(shù)接口java8中旁钧,java8還有一些擴(kuò)展api吸重,例如,List.removeIf() 方法歪今,最后一個(gè)部分看見用這個(gè)實(shí)現(xiàn) 嚎幸,參數(shù)需要一個(gè)條件,如果條件返回true就直接刪除元素寄猩,對(duì)比之前示例嫉晶,我們必須返回true但我們想保存元素:

void removeAll(List<Integer> list, int element) {
    list.removeIf(n -> Objects.equals(n, element));
}

效果和其他一樣:

// given
List<Integer> list = list(1, 1, 2, 3);
int valueToRemove = 1;
 
// when
removeAll(list, valueToRemove);
 
// then
assertThat(list).isEqualTo(list(2, 3));

實(shí)際上,List 本身實(shí)現(xiàn)該方法田篇,我們可以放心假定替废,這種方式性能最好,在以上方法中這個(gè)方案是最簡(jiǎn)潔代碼

9.總結(jié)

本文介紹許多方式解決簡(jiǎn)單問題斯辰,包括錯(cuò)誤示例舶担,分析他們找出最好解決方案
參考地址
github地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市彬呻,隨后出現(xiàn)的幾起案子衣陶,更是在濱河造成了極大的恐慌,老刑警劉巖闸氮,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件剪况,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蒲跨,警方通過查閱死者的電腦和手機(jī)译断,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來或悲,“玉大人孙咪,你說我怎么就攤上這事⊙灿铮” “怎么了翎蹈?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長男公。 經(jīng)常有香客問我荤堪,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任澄阳,我火速辦了婚禮拥知,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘碎赢。我一直安慰自己低剔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布揩抡。 她就那樣靜靜地躺著户侥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪峦嗤。 梳的紋絲不亂的頭發(fā)上蕊唐,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音烁设,去河邊找鬼替梨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛装黑,可吹牛的內(nèi)容都是我干的副瀑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼恋谭,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼糠睡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起疚颊,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤狈孔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后材义,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體均抽,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年其掂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了油挥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡款熬,死狀恐怖深寥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情贤牛,我是刑警寧澤翩迈,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站盔夜,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜喂链,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一返十、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧椭微,春花似錦洞坑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至本慕,卻和暖如春排拷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背锅尘。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來泰國打工监氢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人藤违。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓浪腐,卻偏偏與公主長得像,于是被迫代替她去往敵國和親顿乒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子议街,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 四、集合框架 1:String類:字符串(重點(diǎn)) (1)多個(gè)字符組成的一個(gè)序列璧榄,叫字符串特漩。生活中很多數(shù)據(jù)的描述都采...
    佘大將軍閱讀 752評(píng)論 0 2
  • ? 在編寫java程序中,我們最常用的除了八種基本數(shù)據(jù)類型犹菱,String對(duì)象外還有一個(gè)集合類拾稳,在我們的的程序中到處...
    Java幫幫閱讀 1,420評(píng)論 0 6
  • 一、基礎(chǔ)知識(shí):1腊脱、JVM访得、JRE和JDK的區(qū)別:JVM(Java Virtual Machine):java虛擬機(jī)...
    殺小賊閱讀 2,378評(píng)論 0 4
  • http://python.jobbole.com/85231/ 關(guān)于專業(yè)技能寫完項(xiàng)目接著寫寫一名3年工作經(jīng)驗(yàn)的J...
    燕京博士閱讀 7,575評(píng)論 1 118
  • 最近在慕課網(wǎng)學(xué)習(xí)廖雪峰老師的Python進(jìn)階課程,做筆記總結(jié)一下重點(diǎn)陕凹。 基本變量及其類型 變量 在Python中悍抑,...
    victorsungo閱讀 1,681評(píng)論 0 5