Java Top.

开始使用Spring 5和Spring Boot 2,通过学习春天课程:

>>查看课程

1.介绍

在Java中,从a中删除特定值很简单列表使用list.remove()。然而,有效地删除所有值的所有出现更难。

在本教程中,我们将看到这个问题的多种解决方案,并描述其优缺点。

为了可读性,我们使用了一个自定义列表(int…)测试中的方法,返回一个ArrayList包含我们传递的元素。

2.使用一个尽管环形

既然我们知道如何删除单个元素,并在循环中重复执行看起来很简单:

void removeAll(List List, int element) {while (List .contains(element)) {List .remove(element);}}

然而,它并不像预期的那样工作:

//给定List List = List (1, 2, 3);int valueToRemove = 1;//当asserthatthownby(() - > removeall(list,valueetoremove)).isinstanceof(IndexOufBoundsexception.class);

问题在第三行:我们打电话list.remove(int),这将其参数视为索引,而不是我们想要删除的价值。

在上面的测试中,我们总是调用list.remove (1),但是我们想要删除的元素的索引是0.打电话list.remove()将删除后的所有元素移到更小的索引。

在这个场景中,这意味着我们删除所有元素,除了第一个元素。

只有第一个遗骸,索引1将是非法的。因此我们得到了一个异常

注意,只有我们打电话,我们才面临这个问题list.remove()与原始字节,短,炭int参数,由于编译器尝试找到匹配的过载方法时的第一件事是加宽。

我们可以通过将值传递为整数:

void removeall(列表<整数>列表,整数元素){while(list.Contains(元素)){list.remove(元素);}}

现在代码按预期工作:

//给定List List = List (1, 2, 3);int valueToRemove = 1;//当removeAll(list, valueToRemove);//然后asserthat(列表).isequalto(名单(2,3));

List.contains ()list.remove()两者都必须找到元素的第一个出现,此代码会导致不必要的元素遍历。

如果我们存储第一次出现的索引,我们可以做得更好:

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

我们可以验证它的工作:

//给定List List = List (1, 2, 3);int valueToRemove = 1;//当removeAll(list, valueToRemove);//然后asserthat(列表).isequalto(名单(2,3));

虽然这些解决方案产生了简短而干净的代码,他们的表现仍然很差:因为我们不会跟踪进度,list.remove()必须找到第一个出现的所提供的值来删除它。

当我们使用anArrayList,元素移位可能导致许多参考复印,甚至几次重新分配支持阵列。

3.去除直到列表变化

列表。删除元素(E)有一个我们没有提到的功能:它返回一个布尔值,这是真正的如果列表由于操作而改变,因此它包含了元素

注意,列表。删除(int指数)返回void,因为如果提供的索引有效,则列表总是删除它。否则,它将IndexOutOfBoundsException

有了它,我们就可以执行删除操作,直到列表变化:

void removeAll(List List, int element) {while (List .remove(element));}

它按预期工作:

//给定List List = List (1, 1, 2, 3);int valueToRemove = 1;//当removeAll(list, valueToRemove);//然后asserthat(列表).isequalto(名单(2,3));

尽管短缺,但这种实施遭受了我们在上一节中描述的相同问题。

3.使用A.环形

可以通过使用a遍历元素来跟踪进程循环并删除当前匹配的:

void removeAll(List List, int element) {for (int i = 0;我< list.size ();i++) {if (object . i++);= (element, list.get(i))) {list.remove(i);}}}

它按预期工作:

//给定List List = List (1, 2, 3);int valueToRemove = 1;//当removeAll(list, valueToRemove);//然后asserthat(列表).isequalto(名单(2,3));

但是,如果我们用不同的输入尝试它,它提供了一个不正确的输出:

//给定List List = List (1, 1, 2, 3);int valueToRemove = 1;//当removeAll(list, valueToRemove);/ /然后为了(列表)。isEqualTo(列表(1、2、3);

让我们分析代码的工作原理,逐步:

  • 我= 0.
    • 元素list.get(i)都等于1在第3行,因此Java进入如果声明中,
    • 删除index处的元素0,
    • 所以列表现在包含1,23.
  • 我= 1
    • list.get(i)返回2因为当从列表,它将所有前面的元素转移到更小的索引

所以我们面临这个问题我们有两个相邻的值,我们想去掉它们。为了解决这个问题,我们应该维护循环变量。

当我们删除元素时减少它:

void removeAll(List List, int element) {for (int i = 0;我< list.size ();i++) {if (object . i++);= (element, list.get(i))) {list.remove(i);我,;}}}

只有在不删除元素时才增加:

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

注意,在后者,我们删除了该声明I ++在第2行。

这两种解决方案都按预期工作:

//给定List List = List (1, 1, 2, 3);int valueToRemove = 1;//当removeAll(list, valueToRemove);//然后asserthat(列表).isequalto(名单(2,3));

乍一看,这个实现似乎是正确的。然而,它仍然存在严重的性能问题:

  • 对象中移除元素ArrayList,将所有项移到后面
  • 对象中的索引访问元素LinkedList表示逐个遍历元素,直到找到下标

4.使用一个for - each环形

由于Java 5,我们可以使用for - each循环来遍历列表。让我们用它来删除元素:

void removeall(列表<整数>列表,int元素){for(整数号码:list){if(对象.equals(numbersequals)){list.remove(number);}}}

注意,我们用整数作为循环变量的类型。所以我们不会得到aNullPointerException

同样,这种方式我们调用列表。删除元素(E),它期望我们想要删除的价值,而不是索引。

不幸的是,如同清洁,不幸,它不起作用:

//给定List List = List (1, 1, 2, 3);int valueToRemove = 1;//当assertThatThrownBy(() -> removeWithForEachLoop(list, valueToRemove)) .isInstanceOf(ConcurrentModificationException.class);

for - each循环使用迭代器遍历元素。然而,当我们修改列表,迭代器进入不一致的状态。因此,把并发修改异常

教训是:我们不应该修改a列表当我们访问a中的元素时for - each循环。

5.使用一个迭代器

我们可以使用迭代器直接遍历和修改列表用它:

void removeAll(List List, int element) {for (Iterator i = List . Iterator ();i.hasNext();) {Integer number = i.next();如果对象。= (number, element)) {i.remove();}}}

这条路,迭代器可以跟踪的状态列表(因为它使修改)。因此,上面的代码按预期工作:

//给定List List = List (1, 1, 2, 3);int valueToRemove = 1;//当removeAll(list, valueToRemove);//然后asserthat(列表).isequalto(名单(2,3));

由于每一个列表类可以提供自己的迭代器实现,我们可以安全地假设它以最有效的方式实现元素遍历和删除。

然而,使用ArrayList仍然意味着很多元素转移(也许阵列重新分配)。此外,上面的代码略难读取,因为它与标准不同循环,大多数开发人员都很熟悉。

6.收集

直到这个,我们修改了原始列表对象,删除不需要的项。相反,我们可以创建一个新的列表收集我们想要保留的东西:

List removeAll(List List, int element) {List remainingElements = new ArrayList<>();for(整数号码:列表){if(!objects.equals(number,元素)){RemainseLements.add(数字);}} return remainingElements;}

由于我们在一个新的列表对象,则必须从该方法返回它。因此,我们需要以另一种方式使用该方法:

//给定List List = List (1, 1, 2, 3);int valueToRemove = 1;//当List result = removeAll(List, valueToRemove);/ /然后为了(结果)。isEqualTo(列表(2、3));

注意,现在我们可以使用for - each循环以来我们不会修改列表我们目前正在迭代。

因为没有任何删除操作,所以不需要移动元素。因此,当我们使用ArrayList。

此实现的行为与早期的方式不同:

  • 它不会修改原始列表返回一个新的一个
  • 该方法决定返回的内容列表的实施是,它可能与原始不同

此外,我们可以修改我们的实现恢复原来的行为;我们清除了原始的列表并将收集到的元素添加到其中:

void removeall(list <整数>列表,int元素){list <整数> empaine1ements = new arraylist <>();for(整数号码:列表){if(!objects.equals(number,元素)){RemainseLements.add(数字);list.clear();list.addall(RemainseSements);}

它以前的方式工作:

//给定List List = List (1, 1, 2, 3);int valueToRemove = 1;//当removeAll(list, valueToRemove);//然后asserthat(列表).isequalto(名单(2,3));

因为我们没有修改列表不断,我们不必按位置访问元素或转移它们。此外,只有两个可能的数组重新分配:当我们打电话时List.clear ()List.addAll ()

7.使用Stream API

Java 8引入了Lambda表达式和流API。通过这些强大的功能,我们可以用非常干净的代码解决我们的问题:

列表<整数> removeall(列表<整数>列表,int元素){return list.stream().filter(e->!Objects.Equals.Equals(E,Element)).Collect(收集器.Tolist());}

这个解决方案原理是一样的,就像我们收集剩下的元素一样。

因此,它具有相同的特征,我们应该使用它来返回结果:

//给定List List = List (1, 1, 2, 3);int valueToRemove = 1;//当List result = removeAll(List, valueToRemove);/ /然后为了(结果)。isEqualTo(列表(2、3));

注意,我们可以使用与原始的“收集”实现相同的方法将其转换为与其他解决方案一样的工作方式。

8.使用removeIf

使用Lambdas和功能界面,Java 8也引入了一些API扩展。例如,List.removeIf ()方法,实现了我们在最后一节中所看到的

它期待A.谓词,它应该返回真正的当我们想要移除元素,与前面的例子相反,在那里我们必须返回真正的当我们想要保留元素时:

void removeAll(List List, int element) {List。removeIf (n - >对象。= (n,元素));}

它的工作原理类似于上面的其他解决方案:

//给定List List = List (1, 1, 2, 3);int valueToRemove = 1;//当removeAll(list, valueToRemove);//然后asserthat(列表).isequalto(名单(2,3));

由于这个事实,那个列表本身实现这种方法,我们可以安全地假设,它具有最佳性能。最重要的是,此解决方案提供了所有的最干净的代码。

9.结论

在本文中,我们看到了许多解决简单问题的方法,包括不正确的方法。我们对它们进行了分析,以找到每个场景的最佳解决方案。

像往常一样,可以使用示例在github上

Java底部

开始使用Spring 5和Spring Boot 2,通过学习春天课程:

>>查看课程
2评论
最老的
最新
内联反馈
查看所有评论
评论在本文上关闭!