从列表中删除对象的最佳方法是什么?

9

我有以下逻辑来删除系统中的非活跃用户,因为我们不能在列表迭代时删除一行。是否有更好的方法来处理这个问题?

List<User> users = new ArrayList<User>();
List<User> removeUsers = new ArrayList<User>();

for (User user : users) {
  if (!user.isActive()) {
      removeUsers.add(user);
  }
}

users.removeAll(removeUsers);
9个回答

13
如果您使用ArrayList,则最好使用Jeff M的变体。您也可以使用自己的变体,但是在删除用户时应该考虑使用Set(HashSet或IdentityHashSet)而不是ArrayList。对于大量数据,这将具有更好的性能。
但对于LinkedList,最好使用Iterator.remove方法:
for (Iterator<User> it = users.iterator(); it.hasNext();)
    if (it.next().isActive())
        it.remove();

哦,没错,Iterator.remove() 对于 ArrayList 也是有效的。这样会非常干净利落。 - Jeff Mercado

8

在我看来,这是一种完全可行的方法。

我可能会采用其他方式,使用索引并在反向循环中删除。

for (int i = users.size()-1; i >= 0; i--)
{
    if (!users.get(i).isActive())
    {
        users.remove(i);
    }
}

或者创建一个新的项目列表并用它替换旧的列表。
List<User> newUsers = new ArrayList<User>();
for (User user : users)
{
    if (user.isActive())
    {
        newUsers.add(user);
    }
}
users = newUsers;

目前暂时想不到其他的。


@abyx:太多杂乱无章的东西了吗?这是有争议的。但这并不意味着它不合法。当然,我可以在我的答案中添加使用Iterator.remove()的方法,这看起来更干净,我同意。但我选择引用另一个人的(同样好的)答案。 - Jeff Mercado
1
我必须点赞这个,只是因为 abyx 给它点了踩。 - willcodejavaforfood
+1 "你可以反向迭代并在遍历ArrayList时进行删除。这样做的优点是后续元素不需要移位,且比向前移动更易于编程。" 请参见此问题的第二个答案:https://dev59.com/XXM_5IYBdhLWcg3wmkUK - Ultimate Gobblement
1
哦,我倾向于避免使用foreach循环从列表中删除项目,因为即使在单线程应用程序中,我也看到它们抛出ConcurrentModificationExceptions异常。我曾经在网上查过这个问题,显然是由于Java中的某些bug引起的。 - Ultimate Gobblement
把下面这段与程序相关的内容从英语翻译成中文:仅返回翻译后的文本: - Ultimate Gobblement
显示剩余5条评论

4

使用一些Guava魔法如何?

List<User> users = new ArrayList<User>();
Iterables.removeIf(users, new Predicate<User>() {

 @Override
 public boolean apply(User user) {
  return !user.isActive();
 }
});

如果您在多个地方使用Predicate,甚至可以创建一个命名类来优化代码:
private static final class IsNotActiveUserPredicate implements Predicate<User> {
 @Override
 public boolean apply(User user) {
  return !user.isActive();
 }
}

List<User> users = new ArrayList<User>();
Iterables.removeIf(users, new IsNotActiveUserPredicate());

3
高斯林(Gosling)希望我们使用Iterator.remove来完成此操作:
Iterator<User> it = users.iterator();
while (it.hasNext()) {
    if (! it.next().isActive()) {
        it.remove();
    }
}

如果您正在使用 ArrayList,从性能角度来看,这可能不是最佳选择,但似乎您可能想考虑更改为 LinkedList

无论如何,在迭代集合时删除元素的方法就是这样。


迭代器不是故障安全的。结构性地修改数据结构是不好的。 - Dead Programmer
@Suresh 另一个问题,您说的“iterator is not fail safe”是什么意思? - zengr
1
@Zengr 在迭代过程中,您不应该删除任何元素,否则 hasNext 和 next 将失败,因为您没有通知迭代器类进行数据结构修改(即,您最近已经删除了一个元素,但在旧的迭代器对象上运行)。 - Dead Programmer
1
@Suresh - 请阅读解决方案,我们正在调用Iterator.remove,该迭代器正在执行修改操作,它怎么可能不知道呢?这个解决方案是有效的,随时可以验证。 - abyx
@zengr 抱歉我没有清楚地说明情况,如果两个线程进入您的代码块,就会发生这种情况。 - Dead Programmer
@zengr 请阅读以下帖子 https://dev59.com/dVDTa4cB1Zd3GeqPL8WU - Dead Programmer

1
你可以这样做:
for (int i = users.size()-1; i >= 0; i--) {
  if (!users.get(i).isActive()) {
    users.remove(i);
  }
}

1

我知道这是一个老问题,但我在搜索时找到了它,其他人可能仍然会来到这里。使用Java 8,最简单的方法是像这样在Collection类上使用新的“removeIf”方法:

users.removeIf(user -> !user.isActive());

简单易懂!需要注意的是,在代码底层,Java 代码使用了 Iterator.remove()。

Oracle 文档


0

这是另一个使用Collections2.filter的Guava版本:

final List<User> activeUsers =
    Lists.newArrayList(Collections2.filter(userList, new Predicate<User>(){

        @Override
        public boolean apply(final User input){
            return input.isActive();
        }
    }));

0

这里有另一种使用基于数组的列表非常高效的方法。这是因为它不需要在每次删除时复制整个尾部。它分为两步:首先将需要保留的所有元素复制到列表开头的最终位置,然后删除列表末尾的所有剩余元素。

public static void removeInactiveUsers( ArrayList<User> users )
{

    int j = 0, len = users.size();
    for( int i = 0; i < len; ++i )
    {
        User user = user.get( i );
        if( user.isActive() )
        { 
            if( i != j )
                users.set( j, user );
            ++j;
        }
    }
    users.removeRange( j, len );
}

0
如果您想删除任何一个对象,比如当前对象或选定的对象,可以按照以下步骤进行操作。
    Object currentObject = null;
    for (User user : users)
    {
        if (user.isActive())
        {
            currentObject  = user ;
        }
    }

    if(currentObject != null) {
    users.remove(currentObject);
    }

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