Это вопрос интервью, который был задан в недавнем интервью. Этот блог суммирует и делится этим вопросом.
1. Распространенные ошибки новичков
Возможно, многие новички (включая меня тогда, ха-ха) сначала подумали о методе написания следующим образом:
public static void main(String[] args) {
List<String> platformList = new ArrayList<>();
platformList.add("博客园");
platformList.add("CSDN");
platformList.add("掘金");
for (String platform : platformList) {
if (platform.equals("博客园")) {
platformList.remove(platform);
}
}
System.out.println(platformList);
}
Потом я его уверенно запускал, и оказалось, что его выбросили.java.util.ConcurrentModificationException
Ненормальный, в переводе на китайский язык: исключение одновременной модификации.
Вы сбиты с толку, задаваясь вопросом, почему это так?
Давайте сначала посмотрим на байт-код, сгенерированный приведенным выше кодом, следующим образом:
Из этого видно, что когда цикл foreach фактически выполняется, он фактически используетIterator
, используется основной методhasnext()
иnext()
.
Тогда посмотрите, как реализован Iterator класса ArrayList?
Видно, что вызовnext()
Когда метод получает следующий элемент, вызывается первая строка кодаcheckForComodification();
, и основная логика этого метода заключается в сравненииmodCount
иexpectedModCount
значения этих 2-х переменных.
В приведенном выше примере только начинаетсяmodCount
иexpectedModCount
Значение равно 3, поэтому получить элемент "blog garden" в первый раз не проблема, но при выполнении следующей строки кода:
platformList.remove(platform);
modCount
Значение изменено на 4.
Итак, при получении элемента во второй разmodCount
иexpectedModCount
Значение не равно, поэтому выбрасываетсяjava.util.ConcurrentModificationException
аномальный.
Поскольку мы не можем использовать foreach для этого, как мы можем это сделать?
Существует три основных метода:
- Используйте метод итератора remove()
- Обход в положительном порядке с использованием цикла for
- Обход в обратном порядке с использованием цикла for
Далее я объясню их один за другим.
2. Используйте метод remove() Iterator
Реализация метода remove() с помощью Iterator выглядит следующим образом:
public static void main(String[] args) {
List<String> platformList = new ArrayList<>();
platformList.add("博客园");
platformList.add("CSDN");
platformList.add("掘金");
Iterator<String> iterator = platformList.iterator();
while (iterator.hasNext()) {
String platform = iterator.next();
if (platform.equals("博客园")) {
iterator.remove();
}
}
System.out.println(platformList);
}
Результат:
[CSDN, Самородки]
зачем использоватьiterator.remove();
Может ли это быть?
Давайте посмотрим на его исходный код:
Видно, что каждый раз, когда элемент удаляется,modCount
переназначить значениеexpectedModCount
, чтобы две переменные были равны и не вызывали срабатыванияjava.util.ConcurrentModificationException
аномальный.
3. Используйте цикл for для обхода в положительном порядке
Реализация обхода в прямом порядке с использованием цикла for выглядит следующим образом:
public static void main(String[] args) {
List<String> platformList = new ArrayList<>();
platformList.add("博客园");
platformList.add("CSDN");
platformList.add("掘金");
for (int i = 0; i < platformList.size(); i++) {
String item = platformList.get(i);
if (item.equals("博客园")) {
platformList.remove(i);
i = i - 1;
}
}
System.out.println(platformList);
}
Этот способ реализации прост для понимания, то есть удалять через нижний индекс массива, но есть нюанс, что после удаления элемента значение нижнего индекса нужно исправить:
i = i - 1;
Зачем исправлять значение индекса?
Потому что индекс элемента в начале такой:
После того, как первый цикл удалит элемент «блог-сад», нижний индекс элемента становится следующим:
Во втором цикле значение i равно 1, то есть получается элемент «Самородок», из-за чего элемент «CSDN» пропускается и проверяется, поэтому после удаления элемента нужно исправить индекс, который также выше в кодеi = i - 1;
использование.
4. Используйте цикл for для обхода в обратном порядке
Реализация обхода в обратном порядке с использованием цикла for выглядит следующим образом:
public static void main(String[] args) {
List<String> platformList = new ArrayList<>();
platformList.add("博客园");
platformList.add("CSDN");
platformList.add("掘金");
for (int i = platformList.size() - 1; i >= 0; i--) {
String item = platformList.get(i);
if (item.equals("掘金")) {
platformList.remove(i);
}
}
System.out.println(platformList);
}
Этот метод реализации похож на использование цикла for для обхода в положительном порядке, но нет необходимости изменять нижний индекс, потому что нижний индекс элемента в начале выглядит следующим образом:
После того, как первый цикл удалит элемент «Самородок», нижний индекс элемента становится следующим:
Во втором цикле значение i равно 1, то есть получается элемент «CSDN», что не приведет к пропуску элемента, поэтому нет необходимости модифицировать индекс.
5. Комментарии для прояснения сомнений (обновлено 15 июня 2020 г.)
5.1 Используйте метод removeIf() (рекомендуется)
Из JDK1.8 вы можете использоватьremoveIf()
метод заменыIterator
изremove()
В методе реализовано удаление при обходе, по сути IDEA еще и подскажет:
Итак, исходный код:
Iterator<String> iterator = platformList.iterator();
while (iterator.hasNext()) {
String platform = iterator.next();
if (platform.equals("博客园")) {
iterator.remove();
}
}
Его можно упростить до 1 строки кода, как показано ниже, что очень кратко:
platformList.removeIf(platform -> "博客园".equals(platform));
Глядя на исходный код метода removeIf(), вы обнаружите, что нижний слой также используется.Iterator
изremove()
метод:
5.2 При использовании цикла for для обхода в положительном порядке нужно ли исправлять нижний индекс?
Вывод: нужен.
Однако примеры, приведенные в предыдущей статье, не очень хороши, поэтому многие читатели думают, что можно не исправлять индекс после прочтения, но это не так, давайте разберемся на другом примере:
List<String> platformList = new ArrayList<>();
platformList.add("博客园");
platformList.add("博客园");
platformList.add("CSDN");
platformList.add("掘金");
for (int i = 0; i < platformList.size(); i++) {
String item = platformList.get(i);
if ("博客园".equals(item)) {
platformList.remove(i);
}
}
System.out.println(platformList);
Выходной результат:
[Парк блогов, CSDN, Наггетс]
Можно обнаружить, что если индекс не исправлен, второй элемент «блог-сад» будет пропущен при обходе цикла, и его нельзя будет удалить, поэтому индекс необходимо исправить: