Сторона Hikvision: в чем разница между Iterator и Iterable?

Java задняя часть
Сторона Hikvision: в чем разница между Iterator и Iterable?

В тот день Сяо Эр отправился в Hikvision на собеседование, и как только интервьюер Фараон подошел, он задал ему вопрос: в чем разница между Iterator и Iterable? Сяо Эр чуть не рассмеялся, потому что год назад, в 2021 году, онПуть к продвинутым Java-программистам"62-я статья в колонке увидела этот вопрос 😆.

PS: Для таких вещей, как звездочки, вы можете только попросить об этом. Если вы не попросите об этом, это не будет иметь никакого эффекта. Да ладно. «Дорога к продвинутым Java-программистам» уже получила 460 звезд на GitHub, друзья, спешите нажать и набрать 500!

GitHub.com/IT Ван Эр/до…


В Java, когда мы перемещаемся по списку, в основном есть три пути.

Первый: цикл for.

for (int i = 0; i < list.size(); i++) {
    System.out.print(list.get(i) + ",");
}

Второй: итераторы.

Iterator it = list.iterator();
while (it.hasNext()) {
    System.out.print(it.next() + ",");
}

Третий: для каждого.

for (String str : list) {
    System.out.print(str + ",");
}

Мы пропускаем первый, второй использует итератор, а третий выглядит как for-each, но на самом деле за ним стоит итератор, просто посмотрите на декомпилированный код, чтобы понять.

Iterator var3 = list.iterator();

while(var3.hasNext()) {
    String str = (String)var3.next();
    System.out.print(str + ",");
}

for-each — это просто синтаксический сахар, чтобы сделать наш код более кратким и понятным при переборе списка.

Iterator — это интерфейс, который существует с JDK 1.2 для улучшения перечисления:

  • Разрешить удаление элементов (добавлен метод удаления)
  • Оптимизированные имена методов (hasMoreElements и nextElement в Enumeration, не краткие)

Взгляните на исходный код Iterator:

public interface Iterator<E> {
    // 判断集合中是否存在下一个对象
    boolean hasNext();
    // 返回集合中的下一个对象,并将访问指针移动一位
    E next();
    // 删除集合中调用next()方法返回的对象
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
}

В JDK 1.8 в интерфейс Iterable был добавлен метод forEach:

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

Он выполняет заданную операцию над каждым элементом Iterable, и конкретная операция должна быть написана сама по себе и вызвана методом accept через интерфейс Consumer.

List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
list.forEach(integer -> System.out.println(integer));

Написано более ясно и легко, это:

List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
list.forEach(new Consumer<Integer>() {
    @Override
    public void accept(Integer integer) {
        System.out.println(integer);
    }
});

Если мы внимательно проследим за «бухгалтерской книгой» ArrayList или LinkedList, мы обнаружим, что тень Iterator напрямую не найдена.

Вместо этого найден Iterable!

public interface Iterable<T> {
    Iterator<T> iterator();
}

То есть Iterator не используется непосредственно в реляционном графе List, но Iterable используется для перехода.

Вернемся назад и рассмотрим второй способ обхода списка.

Iterator it = list.iterator();
while (it.hasNext()) {
}

Обнаружил, что он просто совпал. Возьмем, к примеру, ArrayList, который переопределяет метод итератора интерфейса Iterable:

public Iterator<E> iterator() {
    return new Itr();
}

Возвращаемый объект Itr — это внутренний класс, реализующий интерфейс Iterator и по-своему переопределяющий hasNext, next, remove и другие методы.

private class Itr implements Iterator<E> {

    public boolean hasNext() {
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        Object[] elementData = ArrayList.this.elementData;
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

}

Тогда некоторые друзья могут спросить: почему бы не поместить основные методы hasNext и next в Iterator напрямую в интерфейс Iterable? Не было бы удобнее использовать его напрямую, как показано ниже?

Iterable it = list.iterator();
while (it.hasNext()) {
}

Из грамматики суффиксов английских слов (Iterable)able означает, что этот список поддерживает итерацию, а (Iterator)tor означает, как этот список повторяется.

Очевидно, что поддерживающую итерацию и конкретную итерацию нельзя смешивать, иначе будет бардак. До сих пор хорошо делают свою работу.

Подумайте об этом, если итератор и Iterable объединены, сложно ли сделать для каждого способ обхода списка?

В принципе, пока List реализует интерфейс Iterable, его можно пройти с помощью for-each.Как его пройти, зависит от того, как он реализует интерфейс Iterator.

Невозможно использовать for-each непосредственно на Map, потому что Map не реализует интерфейс Iterable, только черезmap.entrySet(),map.keySet(),map.values()Этот способ возврата коллекции позволяет использовать for-each.

Если мы внимательно изучим исходный код LinkedList, то обнаружим, что LinkedList не переопределяет напрямую метод итератора интерфейса Iterable, а дополняется своим родительским классом AbstractSequentialList.

public Iterator<E> iterator() {
    return listIterator();
}

LinkedList переопределяет метод listIterator:

public ListIterator<E> listIterator(int index) {
    checkPositionIndex(index);
    return new ListItr(index);
}

Здесь мы нашли новый итератор ListIterator, который наследует интерфейс Iterator, при обходе списка он может начинать обход с любого нижнего индекса и поддерживает двунаправленный обход.

public interface ListIterator<E> extends Iterator<E> {
    boolean hasNext();
    E next();
    boolean hasPrevious();
    E previous();
}

Мы знаем, что в коллекции (Collection) есть не только List, но и Map и Set, значит Iterator поддерживает не только List, но и Set, а ListIterator поддерживает только List.

Тогда некоторые друзья могут спросить: почему бы не позволить List реализовать интерфейс Iterator напрямую, а использовать для его реализации внутренние классы?

Это связано с тем, что некоторые списки могут иметь несколько методов обхода, например LinkedList.Помимо поддержки методов обхода в положительном порядке, они также поддерживают методы обхода в обратном порядке — DescendingIterator:

private class DescendingIterator implements Iterator<E> {
    private final ListItr itr = new ListItr(size());
    public boolean hasNext() {
        return itr.hasPrevious();
    }
    public E next() {
        return itr.previous();
    }
    public void remove() {
        itr.remove();
    }
}

Можно видеть, что DescendingIterator просто использует то, как ListIterator перемещается вперед. Его можно использовать следующими способами:

Iterator it = list.descendingIterator();
while (it.hasNext()) {
}

Что ж, давайте сначала поговорим об Iterator и Iterable и резюмируем два момента:

  • Научитесь глубоко мыслить, немного разденьте кокон и больше думайте о том, почему это достигается именно таким образом.Многие задачи не так сложны, как вы думаете.
  • Не сдавайтесь, когда вы сталкиваетесь с сомнениями, это лучшая возможность для самосовершенствования, когда вы сталкиваетесь с трудным моментом, вы найдете много связанных вещей в процессе его решения.

Это 62-я часть рубрики "Путь к продвинутым программистам Java". Продвинутый путь программистов Java, юмористический, простой для понимания, чрезвычайно дружелюбный и удобный для начинающих Java😘, включая, помимо прочего, синтаксис Java, структуру сбора Java, Java IO, параллельное программирование Java, виртуальную машину Java и другие основные знания..

GitHub.com/IT Ван Эр/до…

Вау! Оффлайн-версия PDF тоже очень красивая~