你好!在上一讲中,我们熟悉了ArrayList类,并学习了如何使用它执行最常见的操作。此外,我们还强调了 ArrayList 和常规数组之间的一些差异。现在让我们看看从 ArrayList 中删除一个元素。我们已经说过,删除常规数组中的元素并不是很方便。由于我们无法删除单元格本身,因此我们只能将其值“归零”:
public class Cat {
private String name;
public Cat(String name) {
this.name = name;
}
public static void main(String[] args) {
Cat[] cats = new Cat[3];
cats[0] = new Cat("Thomas");
cats[1] = new Cat("Hippopotamus");
cats[2] = new Cat("Philip Markovich");
cats[1] = null;
System.out.println(Arrays.toString(cats));
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
结论:
[Cat{name='Томас'}, null, Cat{name='Фorпп Маркович'}]
但重置后,阵列中仍留有一个“洞”。我们不会删除单元格,而只会删除其内容。想象一下,如果我们有一个包含 50 只猫的数组,我们以这种方式删除了其中 17 只,会发生什么。我们将有一个有 17 个孔的阵列,并照顾它们!记住可以写入新值的空单元格的数量是不现实的。犯一次错误,您就会用所需的对象引用覆盖单元格。当然,有机会做得更仔细一点:删除后,将数组的元素移到开头,这样“洞”就在末尾:
public static void main(String[] args) {
Cat[] cats = new Cat[4];
cats[0] = new Cat("Thomas");
cats[1] = new Cat("Hippopotamus");
cats[2] = new Cat("Philip Markovich");
cats[3] = new Cat("Fluff");
cats[1] = null;
for (int i = 2; i < cats.length-1; i++) {
//move the elements to the beginning so that the empty cell is at the end
cats[i-1] = cats[i];
cats[i] = null;
}
System.out.println(Arrays.toString(cats));
}
结论:
[Cat{name='Томас'}, Cat{name='Фorпп Маркович'}, Cat{name='Пушок'}, null]
现在看起来似乎更好了,但这很难称为稳定的解决方案。至少,因为每次从数组中删除元素时,我们都必须手动编写此代码!糟糕的选择。您可以采用另一种方式并创建一个单独的方法:
public void deleteCat(Cat[] cats, int indexToDelete) {
//...remove the cat by index and shift the elements
}
但这也没什么用处:这个方法只能对对象起作用Cat
,而不能对其他对象起作用。也就是说,如果程序中还有 100 个以上的类,我们要使用数组,则必须在每个类中编写相同的方法,并具有完全相同的逻辑。这是彻底的失败-_- 但是在ArrayList类中这个问题被成功解决了!它实现了一种删除元素的特殊方法 - remove()
:
public static void main(String[] args) {
ArrayList<Cat> cats = new ArrayList<>();
Cat thomas = new Cat("Thomas");
Cat behemoth = new Cat("Hippopotamus");
Cat philipp = new Cat("Philip Markovich");
Cat pushok = new Cat("Fluff");
cats.add(thomas);
cats.add(behemoth);
cats.add(philipp);
cats.add(pushok);
System.out.println(cats.toString());
cats.remove(1);
System.out.println(cats.toString());
}
我们将对象的索引传递给该方法,然后它被删除(就像在数组中一样)。该方法remove()
有两个特点。 首先,它不留“漏洞”。它已经实现了从中间删除元素时移动元素的逻辑,这是我们之前手写的。在控制台中查看前面代码的输出:
[Cat{name='Томас'}, Cat{name='Бегемот'}, Cat{name='Фorпп Маркович'}, Cat{name='Пушок'}]
[Cat{name='Томас'}, Cat{name='Фorпп Маркович'}, Cat{name='Пушок'}]
我们从中间移走了一只猫,然后将其他猫移到周围,以确保没有间隙。 其次,它不仅可以通过索引(如常规数组)删除对象,还可以通过对象的引用来删除对象:
public static void main(String[] args) {
ArrayList<Cat> cats = new ArrayList<>();
Cat thomas = new Cat("Thomas");
Cat behemoth = new Cat("Hippopotamus");
Cat philipp = new Cat("Philip Markovich");
Cat pushok = new Cat("Fluff");
cats.add(thomas);
cats.add(behemoth);
cats.add(philipp);
cats.add(pushok);
System.out.println(cats.toString());
cats.remove(philipp);
System.out.println(cats.toString());
}
结论:
[Cat{name='Томас'}, Cat{name='Бегемот'}, Cat{name='Фorпп Маркович'}, Cat{name='Пушок'}]
[Cat{name='Томас'}, Cat{name='Бегемот'}, Cat{name='Пушок'}]
如果您不想始终将所需对象的索引保留在脑海中,这会非常方便。我们似乎已经解决了通常的删除问题。现在让我们想象一下这种情况:我们想要迭代元素列表并删除具有特定名称的猫。 为此,我们使用一个特殊的循环运算符for
- for each
。您可以在本次讲座中了解更多相关信息。
public static void main(String[] args) {
ArrayList<Cat> cats = new ArrayList<>();
Cat thomas = new Cat("Thomas");
Cat behemoth = new Cat("Hippopotamus");
Cat philipp = new Cat("Philip Markovich");
Cat pushok = new Cat("Fluff");
cats.add(thomas);
cats.add(behemoth);
cats.add(philipp);
cats.add(pushok);
for (Cat cat: cats) {
if (cat.name.equals("Hippopotamus")) {
cats.remove(cat);
}
}
System.out.println(cats);
}
该代码看起来非常合乎逻辑。然而,结果可能会让你大吃一惊:
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 Cat.main(Cat.java:25)
某种错误,并且不清楚为什么会突然出现。在此过程中有许多细微差别需要处理。您需要记住的一般规则: 您不能迭代集合并同时更改其元素。 是的,是的,确实是改变,而不仅仅是删除。如果您尝试在我们的代码中将删除猫替换为插入新猫,结果将是相同的:
for (Cat cat: cats) {
cats.add(new Cat("Salem Saberhegen"));
}
System.out.println(cats);
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 Cat.main(Cat.java:25)
我们将一项操作更改为另一项操作,但结果没有改变:同样的错误ConcurrentModificationException
。当我们在迭代列表时尝试打破规则并更改列表时,就会发生这种情况。在Java中,要在迭代期间删除元素,您需要使用一个特殊的对象 - 迭代器(类Iterator
)。该类Iterator
负责安全地遍历元素列表。它非常简单,因为它只有 3 个方法:
hasNext()
- 返回true
取决于false
列表中是否有下一个元素,或者我们是否已经到达最后一个元素。next()
- 返回列表的下一个元素remove()
- 从列表中删除一个元素
Iterator<Cat> catIterator = cats.iterator();//create an iterator
while(catIterator.hasNext()) {//as long as there are elements in the list
Cat nextCat = catIterator.next();//get next element
System.out.println(nextCat);// print it to the console
}
结论:
Cat{name='Томас'}
Cat{name='Бегемот'}
Cat{name='Фorпп Маркович'}
Cat{name='Пушок'}
正如您所看到的,该类ArrayList
已经实现了一个用于创建迭代器的特殊方法 - iterator()
。另请注意,在创建迭代器时,我们指定了它将使用的对象类 ( <Cat>
)。最终,我们可以使用迭代器轻松解决原始问题。例如,让我们删除一只名为“Philip Markovich”的猫:
Iterator<Cat> catIterator = cats.iterator();//create an iterator
while(catIterator.hasNext()) {//as long as there are elements in the list
Cat nextCat = catIterator.next();//get next element
if (nextCat.name.equals("Philip Markovich")) {
catIterator.remove();//delete the cat with the desired name
}
}
System.out.println(cats);
结论:
[Cat{name='Томас'}, Cat{name='Бегемот'}, Cat{name='Пушок'}]
您可能已经注意到,我们没有在迭代器方法中指定元素索引或引用变量名称remove()
!迭代器比看起来更聪明:该方法remove()
删除迭代器返回的最后一个元素。正如您所看到的,它完全按照需要工作:) 这基本上就是您需要了解的有关从ArrayList
. 更准确地说 - 几乎一切。在下一堂课中,我们将深入了解该类的“内部”,看看操作过程中会发生什么:)再见!
GO TO FULL VERSION