В тот день Сяо Эр отправился в Hikvision на собеседование, и как только интервьюер Фараон подошел, он задал ему вопрос: в чем разница между Iterator и Iterable? Сяо Эр чуть не рассмеялся, потому что год назад, в 2021 году, онПуть к продвинутым Java-программистам"62-я статья в колонке увидела этот вопрос 😆.
PS: Для таких вещей, как звездочки, вы можете только попросить об этом. Если вы не попросите об этом, это не будет иметь никакого эффекта. Да ладно. «Дорога к продвинутым Java-программистам» уже получила 460 звезд на GitHub, друзья, спешите нажать и набрать 500!
В 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 и другие основные знания..
Вау! Оффлайн-версия PDF тоже очень красивая~