Вступление
В то время как мы можем использовать for
или во while
цикла , чтобы
траверса через совокупность элементов, Iterator
позволяет нам делать
это , не заботясь о позициях индекса и даже позволяет не только пройти
через коллекцию, но и изменить его в то же время, что не всегда возможно
for
петель , если вы удаляете элементы в цикле, например.
Добавьте к этому возможность реализовать наш настраиваемый Iterator для итерации по гораздо более сложным объектам, а также перемещения вперед и назад, и преимущества знания того, как его использовать, станут совершенно очевидными.
В этой статье будет довольно подробно рассказано, как можно использовать
интерфейсы Iterator
и Iterable
Итератор ()
Интерфейс Iterator
используется для перебора элементов в коллекции (
List
, Set
или Map
). Он используется для получения элементов по
одному и выполнения операций над каждым из них, если это необходимо.
Вот методы, используемые для обхода коллекций и выполнения операций:
.hasNext()
: возвращаетtrue
если мы не достигли конца коллекции, в противном случаеfalse
.next()
: возвращает следующий элемент в коллекции..remove()
: удаляет последний элемент, возвращенный итератором, из коллекции..forEachRemaining()
: выполняет указанное действие для каждого оставшегося элемента в коллекции в последовательном порядке.
Во-первых, поскольку итераторы предназначены для использования с
коллекциями, давайте ArrayList
простой ArrayList с несколькими
элементами:
List<String> avengers = new ArrayList<>();
// Now lets add some Avengers to the list
avengers.add("Ant-Man");
avengers.add("Black Widow");
avengers.add("Captain America");
avengers.add("Doctor Strange");
Мы можем перебирать этот список, используя простой цикл:
System.out.println("Simple loop example:\n");
for (int i = 0; i < avengers.size(); i++) {
System.out.println(avengers.get(i));
}
Хотя мы хотим изучить итераторы:
System.out.println("\nIterator Example:\n");
// First we make an Iterator by calling
// the .iterator() method on the collection
Iterator<String> avengersIterator = avengers.iterator();
// And now we use .hasNext() and .next() to go through it
while (avengersIterator.hasNext()) {
System.out.println(avengersIterator.next());
}
Что произойдет, если мы захотим удалить элемент из этого ArrayList
?
Попробуем сделать это с помощью обычного цикла for
System.out.println("Simple loop example:\n");
for (int i = 0; i < avengers.size(); i++) {
if (avengers.get(i).equals("Doctor Strange")) {
avengers.remove(i);
}
System.out.println(avengers.get(i));
}
Нас встретит неприятное IndexOutOfBoundsException
:
Simple loop example:
Ant-Man
Black Widow
Captain America
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 3, Size: 3
Это имеет смысл, поскольку мы изменяем размер коллекции по мере ее
прохождения. То же самое и с расширенным циклом for
System.out.println("Simple loop example:\n");
for (String avenger : avengers) {
if (avenger.equals("Doctor Strange")) {
avengers.remove(avenger);
}
System.out.println(avenger);
}
Мы снова встречаем еще одно исключение:
Simple loop example:
Ant-Man
Black Widow
Captain America
Doctor Strange
Exception in thread "main" java.util.ConcurrentModificationException
В этом случае итераторы могут оказаться полезными, выступая в роли посредника для удаления элемента из коллекции, а также для обеспечения продолжения обхода в соответствии с планом:
Iterator<String> avengersIterator = avengers.iterator();
while (avengersIterator.hasNext()) {
String avenger = avengersIterator.next();
// First we must find the element we wish to remove
if (avenger.equals("Ant-Man")) {
// This will remove "Ant-Man" from the original
// collection, in this case a List
avengersIterator.remove();
}
}
Это гарантированно безопасный метод удаления элементов при просмотре коллекций.
И чтобы проверить, был ли удален элемент:
// We can also use the helper method .forEachRemaining()
System.out.println("For Each Remaining Example:\n");
Iterator<String> avengersIteratorForEach = avengers.iterator();
// This will apply System.out::println to all elements in the collection
avengersIteratorForEach.forEachRemaining(System.out::println);
И результат:
For Each Remaining Example:
Black Widow
Captain America
Doctor Strange
Как видите, «Человек-муравей» был удален из списка avengers
ListIterator ()
ListIterator
расширяет интерфейс Iterator
Он используется только в
List
и может выполнять двунаправленную итерацию, что означает, что вы
можете выполнять итерацию спереди назад или сзади к началу. У него также
нет текущего элемента, потому что курсор всегда помещается между двумя
элементами в List
, поэтому мы должны использовать .previous()
или
.next()
для доступа к элементу.
В чем разница между
Iterator
иListIterator
?
Во-первых, Iterator
можно применить к любой коллекции - List
,
Map
, Queue
, Set
и т. Д.
ListIterator
можно применять только к спискам. Добавляя это
ограничение, ListIterator
может быть намного более конкретным, когда
дело доходит до методов, и поэтому мы познакомились с множеством новых
методов, которые помогают нам изменять списки во время обхода.
Если вы имеете дело с реализацией List
ArrayList
, LinkedList
и т.
Д.), Всегда предпочтительнее использовать ListIterator
.
Вот методы, которые вы, вероятно, будете использовать:
.add(E e)
: вставляет элемент в список..remove()
: удаляет последний элемент, который был возвращен.next()
или.previous()
из списка..set(E e)
: заменяет последний элемент, возвращенный.next()
или.previous()
указанным элементом.hasNext()
: возвращаетtrue
если мы не достигли конца списка, в противном случаеfalse
.next()
: возвращает следующий элемент в списке..nextIndex()
: возвращает индекс следующего элемента..hasPrevious()
: возвращаетtrue
если мы не достигли начала списка, в противном случаеfalse
.previous()
: возвращает предыдущий элемент в списке..previousIndex()
: возвращает индекс предыдущего элемента.
Опять же, давайте ArrayList
несколькими элементами:
ArrayList<String> defenders = new ArrayList<>();
defenders.add("Daredevil");
defenders.add("Luke Cage");
defenders.add("Jessica Jones");
defenders.add("Iron Fist");
Давайте воспользуемся ListIterator
чтобы просмотреть список и
распечатать элементы:
ListIterator listIterator = defenders.listIterator();
System.out.println("Original contents of our List:\n");
while (listIterator.hasNext())
System.out.print(listIterator.next() + System.lineSeparator());
Очевидно, он работает так же, как классический Iterator
. Результат:
Original contents of our List:
Daredevil
Luke Cage
Jessica Jones
Iron Fist
Теперь попробуем изменить некоторые элементы:
System.out.println("Modified contents of our List:\n");
// Now let's make a ListIterator and modify the elements
ListIterator defendersListIterator = defenders.listIterator();
while (defendersListIterator.hasNext()) {
Object element = defendersListIterator.next();
defendersListIterator.set("The Mighty Defender: " + element);
}
Распечатка списка сейчас даст:
Modified contents of our List:
The Mighty Defender: Daredevil
The Mighty Defender: Luke Cage
The Mighty Defender: Jessica Jones
The Mighty Defender: Iron Fist
Теперь давайте продолжим и пройдемся по списку в обратном направлении,
что мы можем сделать с помощью ListIterator
:
System.out.println("Modified List backwards:\n");
while (defendersListIterator.hasPrevious()) {
System.out.println(defendersListIterator.previous());
}
И результат:
Modified List backwards:
The Mighty Defender: Iron Fist
The Mighty Defender: Jessica Jones
The Mighty Defender: Luke Cage
The Mighty Defender: Daredevil
Сплитератор ()
Интерфейс Spliterator
функционально такой же, как и Iterator
.
Возможно, вам никогда не понадобится использовать Spliterator
напрямую, но давайте все же рассмотрим некоторые варианты использования.
Однако сначала вам следует немного познакомиться с Java Streams и Lambda Expressions в Java .
Хотя мы перечислим все методы Spliterator
, полная работа
Spliterator
выходит за рамки этой статьи. Одна вещь, которую мы
рассмотрим на примере, - это то, как Spliterator
может использовать
распараллеливание для более эффективного прохождения Stream
который мы
можем разбить.
Spliterator
мы будем использовать следующие методы:
.characteristics()
: Возвращает характеристики , что Spliterator имеет какint
значение. К ним относятся:ORDERED
DISTINCT
SORTED
SIZED
CONCURRENT
IMMUTABLE
NONNULL
SUBSIZED
.estimateSize()
: возвращает оценку количества элементов, которые могут встретиться при обходе, в видеlong
значения или возвращаетlong.MAX_VALUE
если вычислить невозможно..forEachRemaining(E e)
: выполняет указанное действие для каждого оставшегося элемента в коллекции в последовательном порядке..getComparator()
: если источник этогоSpliterator
отсортированComparator
, он возвращает этотComparator
..getExactSizeIfKnown()
: возвращает.estimateSize()
если размер известен, в противном случае возвращает-1
.hasCharacteristics(int characteristics)
: возвращаетtrue
если.characteristics()
Spliterator
содержит все указанные характеристики..tryAdvance(E e)
: если оставшийся элемент существует, выполняет с ним заданное действие, возвращаяtrue
, иначе возвращаетfalse
..trySplit()
: если этотSpliterator
может быть разделен, возвращаетSpliterator
покрывающие, которые после возврата из этого метода не будут охвачены этимSpliterator
.
Как обычно, начнем с простого ArrayList
:
List<String> mutants = new ArrayList<>();
mutants.add("Professor X");
mutants.add("Magneto");
mutants.add("Storm");
mutants.add("Jean Grey");
mutants.add("Wolverine");
mutants.add("Mystique");
Теперь нам нужно применить Spliterator
к Stream
. К счастью,
конвертировать между ArrayList
и Stream
легко благодаря фреймворку
Collections:
// Obtain a Stream to the mutants List.
Stream<String> mutantStream = mutants.stream();
// Getting Spliterator object on mutantStream.
Spliterator<String> mutantList = mutantStream.spliterator();
И чтобы продемонстрировать некоторые из этих методов, давайте запустим каждый из них:
// .estimateSize() method
System.out.println("Estimate size: " + mutantList.estimateSize());
// .getExactSizeIfKnown() method
System.out.println("\nExact size: " + mutantList.getExactSizeIfKnown());
System.out.println("\nContent of List:");
// .forEachRemaining() method
mutantList.forEachRemaining((n) -> System.out.println(n));
// Obtaining another Stream to the mutant List.
Spliterator<String> splitList1 = mutantStream.spliterator();
// .trySplit() method
Spliterator<String> splitList2 = splitList1.trySplit();
// If splitList1 could be split, use splitList2 first.
if (splitList2 != null) {
System.out.println("\nOutput from splitList2:");
splitList2.forEachRemaining((n) -> System.out.println(n));
}
// Now, use the splitList1
System.out.println("\nOutput from splitList1:");
splitList1.forEachRemaining((n) -> System.out.println(n));
И на выходе мы получаем следующее:
Estimate size: 6
Exact size: 6
Content of List:
Professor X
Magneto
Storm
Jean Grey
Wolverine
Mystique
Output from splitList2:
Professor X
Magneto
Storm
Output from splitList1:
Jean Grey
Wolverine
Mystique
Итерируемый ()
Что, если по какой-то причине мы захотим создать собственный интерфейс
Iterator
Первое, с чем вам следует ознакомиться, это этот график:
{.ezlazyload}
Чтобы создать наш собственный Iterator
нам нужно будет написать
собственные методы для .hasNext()
, .next()
и .remove()
.
Внутри Iterable
у нас есть метод, который возвращает итератор для
элементов в коллекции, это метод .iterator()
, и метод, который
выполняет действие для каждого элемента в итераторе, метод .forEach()
.
Например, представим, что мы Тони Старк, и нам нужно написать собственный итератор, чтобы перечислить все костюмы Железного человека, которые есть у вас в арсенале.
Сначала создадим класс для получения и установки данных костюма:
public class Suit {
private String codename;
private int mark;
public Suit(String codename, int mark) {
this.codename = codename;
this.mark = mark;
}
public String getCodename() { return codename; }
public int getMark() { return mark; }
public void setCodename (String codename) {this.codename=codename;}
public void setMark (int mark) {this.mark=mark;}
public String toString() {
return "mark: " + mark + ", codename: " + codename;
}
}
Далее напишем наш собственный Итератор:
// Our custom Iterator must implement the Iterable interface
public class Armoury implements Iterable<Suit> {
// Notice that we are using our own class as a data type
private List<Suit> list = null;
public Armoury() {
// Fill the List with data
list = new LinkedList<Suit>();
list.add(new Suit("HOTROD", 22));
list.add(new Suit("SILVER CENTURION", 33));
list.add(new Suit("SOUTHPAW", 34));
list.add(new Suit("HULKBUSTER 2.0", 48));
}
public Iterator<Suit> iterator() {
return new CustomIterator<Suit>(list);
}
// Here we are writing our custom Iterator
// Notice the generic class E since we do not need to specify an exact class
public class CustomIterator<E> implements Iterator<E> {
// We need an index to know if we have reached the end of the collection
int indexPosition = 0;
// We will iterate through the collection as a List
List<E> internalList;
public CustomIterator(List<E> internalList) {
this.internalList = internalList;
}
// Since java indexes elements from 0, we need to check against indexPosition +1
// to see if we have reached the end of the collection
public boolean hasNext() {
if (internalList.size() >= indexPosition +1) {
return true;
}
return false;
}
// This is our custom .next() method
public E next() {
E val = internalList.get(indexPosition);
// If for example, we were to put here "indexPosition +=2" we would skip every
// second element in a collection. This is a simple example but we could
// write very complex code here to filter precisely which elements are
// returned.
// Something which would be much more tedious to do with a for or while loop
indexPosition += 1;
return val;
}
// In this example we do not need a .remove() method, but it can also be
// written if required
}
}
И, наконец, основной класс:
public class IronMan {
public static void main(String[] args) {
Armoury armoury = new Armoury();
// Instead of manually writing .hasNext() and .next() methods to iterate through
// our collection we can simply use the advanced forloop
for (Suit s : armoury) {
System.out.println(s);
}
}
}
Результат:
mark: 22, codename: HOTROD
mark: 33, codename: SILVER CENTURION
mark: 34, codename: SOUTHPAW
mark: 48, codename: HULKBUSTER 2.0
Заключение
В этой статье мы подробно рассмотрели, как работать с итераторами в
Java, и даже написали собственный, чтобы изучить все новые возможности
интерфейса Iterable
Мы также коснулись того, как Java использует распараллеливание потоков
для внутренней оптимизации обхода через коллекцию с использованием
интерфейса Spliterator