如何在Java中从ArrayList中删除除第一个元素以外的所有内容

28

我刚开始学习Java编程,之前一直在使用PHP,所以我习惯了这种类型的循环:

int size = mapOverlays.size();
for(int n=1;n<size;n++)
{
    mapOverlays.remove(n);
}

所以我想删除除了第一个项目之外的所有内容,那么为什么这不起作用?按照我的理解,在删除后,数组键重新排列了吗?


据我所理解,您正在选择名为itemizedOverlay的列表的大小,并在另一个名为mapOverlays的列表上进行操作。希望这很正常;-) - Agemen
抱歉,打错了,这两个数组是相同的,我已经编辑过了。 - dfilkovi
11个回答

137

你可以使用

mapOverlays.subList(1, mapOverlays.size()).clear();

8
我惊讶于这不是最受欢迎的答案。它既易于编写,而且还可能是最有效率的(因为每个List实现都会根据其结构以最优化的方式执行任务)。 - Kevin Bourrillion
2
哇塞,它在短短一个小时内从2票变成了最高评分! - Kevin Bourrillion
这是一种有用的编写方式。但是,如果您实际查看代码,它使用了受保护的removeRange,并且基本上与Plamena的解决方案完全相同。主要区别在于方法调用的数量以及循环的位置。通过良好的JIT,我认为它们的性能相当类似。显然,它们的大O表示法是相同的。 - Matthew Flaschen
3
这句话的意思是:“这取决于List的实现方式。对于ArrayList(至少在OpenJDK源代码中),removeRange方法调用System.arraycopy一次,不会调用其他任何方法。在这种特定情况下,它移动了0个元素,因此基本上它所做的就是在紧密循环中将数组元素设置为null并更新大小字段。” - Adam Crume

26

我的理解是,在删除数组元素后,数组键会重新排列吗? 是的,当你移除在位置1的元素后,原来在位置2的元素将会被排到位置1。

你可以尝试这个方法:

Object obj = mapOverlays.get(0); // remember first item
mapOverlays.clear(); // clear complete list
mapOverlays.add(obj); // add first item

4
这种方法可能可行,但会导致列表进入不必要的中间状态。如果该列表被并发使用(例如 CopyOnWriteArrayList),则应避免使用此解决方案。 - Jesse Wilson

7

为什么不尝试倒着做呢?

(内容涉及IT技术)
int size = itemizedOverlay.size();
for(int n=size-1;n>0;n--)
{
    mapOverlays.remove(n);
}

2
在某些列表实现中,这将表现得非常糟糕,而且永远不会达到最佳状态。 - Kevin Bourrillion
2
@Kevin,OP并没有使用"some List"。他明确表示这是一个ArrayList。据我所知,对于Java内置的所有列表,包括LinkedList,时间复杂度都是O(n) - Matthew Flaschen

4

ArrayList是一个整数索引从0到size() - 1的集合。你可以这样做:

int size = mapOverlays.size();
for(int n=1;n<size;n++)
{
    mapOverlays.remove(1);
}

这可能符合您对PHP的预期。它的工作原理是不断删除第1个元素,这会改变数组。但是,这种方法性能较差,因为内部数组必须不断向下移位。更好的方法是使用clear()或按相反的顺序进行操作。
很遗憾,removeRange是受保护的,因为这种操作会很方便。

+1 如果指出了您的潜在解决方案的性能劣势。同时,记住您总是可以扩展ArrayList并公开任何受保护的方法。 - Tim Bender
你不需要使用 removeRange 方法(问题不在于它是受保护的,而是它甚至不存在于 List 接口中(这是正确的))。请参考 Adam Crune 的回答。 - Kevin Bourrillion
1
@Kevin,原帖并未使用List接口,所以那是一个非必然的推论。你可以争论他应该使用List接口,但也许他确定他明确想要一个基于数组的类来获得其性能特性。 - Matthew Flaschen

4

我认为创建一个只包含第一个元素的新ArrayList会更快。可以像这样实现:

E temp = mapOverlays.get(0);
mapOverlays = new ArrayList<E>().add(temp);

顺便说一下,丹尼尔的建议可能更好,因为它不会创建一个新对象。因此,对该对象的引用保持不变。 - Agemen
add returns a boolean, so you can’t do mapOverlays = new ArrayList<E>().add(temp); You need two statements, mapOverlays = new ArrayList<E>(); and mapOverlays.add(temp); - Holger

2
简单。
mapOverlays = Collections.singletonList(mapOverlays.get(0));

3
也许不是原帖作者想要的——结果列表只有第一个元素,但您不能再添加任何内容了。因此,这并不是一个错误的答案,但它引入了一些限制。 - Andreas Dolk

0

因为在ArrayList中,第一个元素的索引是0
在Java中正确的方式是:

while( mapOverlays.size() > 1 ) {
  mapOverlays.remove( mapOverlays.size() - 1 );
}

0
如果您使用的是java.util.List实现而不是数组,则每次删除元素时数组的大小都会变小,n+1项将替换n项。当n变得大于列表中的最后一个索引时,此代码最终将导致ArrayIndecOutOfBoundsException
Java还有一种数组类型,其大小无法更改:
Object[] mapOverlay = //initialize the array here
int size = mapOverlay.length;
for(int n=1;n<size;n++)
{
    mapOverlay[n] = null;
}

我不懂 PHP,但这听起来很接近你想要的行为。然而,List 实现比数组更灵活、更舒适。

编辑:这里是 List.remove(int) 的 Javadoc 链接:http://java.sun.com/javase/6/docs/api/java/util/List.html#remove%28int%29


0
int size = mapOverlays.size();
for(int n=0;n<size;n++)
{
    mapOverlays.remove(n);
}

在Java中,如果mapOverlays是列表,则它以0作为第一个索引开始。因此,在for循环中n=0。

我认为OP想要保留列表中的第一个元素并删除其他所有元素。 - user85421

0
使用while循环来删除第一个元素后的所有内容:
while (mapOverlays.size() > 1) {
    mapOverlays.remove(1);
}

编辑(请参见Adam Crume的评论)

如果性能是一个问题,您应该使用这个。

while (mapOverlays.size() > 1) {
    mapOverlays.remove(mapOverlays.size()-1);
}

即使是一点微小的优化

int last = mapOverlays.size() - 1;
while (last >= 1) {
    mapOverlays.remove(last);
    last -= 1;
}

如果性能真的是一个问题(且列表有很多元素),您应该使用sublist解决方案。虽然它有点难以阅读,但如果列表实例无法重新创建(在其他地方引用),那么这可能是最快的解决方案。

对于一个ArrayList,这个操作的时间复杂度非常低效 - O(n^2) - 因为它必须调用System.arraycopy近乎整个数组n-1次。相比之下,删除最后一个元素而不是索引为1的元素会更好。 - Adam Crume
@Adam - 正确,答案已编辑,也许 remove(1) 的解决方案仍然比 O(n^2) 更糟糕,但我更喜欢它用于小列表的可读性更高(依我之见)- 问题根本没有提到性能。sublist 解决方案优雅且更好(最佳),但不是每个人都理解如何使用 sublist - user85421
我不在乎人们是否使用子列表。我只是认为将运行时间从O(n^2)减少到O(n)值得为可读性做出小的牺牲。remove(1)解决方案对于小列表可能很好,但我认为最好拥有适用于任何列表的代码。 - Adam Crume

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接