[Вопросы для интервью с Java] Как просматривать и удалять список одновременно?

Java

Это вопрос интервью, который был задан в недавнем интервью. Этот блог суммирует и делится этим вопросом.

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 для этого, как мы можем это сделать?

Существует три основных метода:

  1. Используйте метод итератора remove()
  2. Обход в положительном порядке с использованием цикла for
  3. Обход в обратном порядке с использованием цикла 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, Наггетс]

Можно обнаружить, что если индекс не исправлен, второй элемент «блог-сад» будет пропущен при обходе цикла, и его нельзя будет удалить, поэтому индекс необходимо исправить:

6. Ссылка

Как пройти коллекцию Java при удалении

Почему я не могу удалять элементы при обходе в java