Вступление
В то время как мы можем использовать 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значение. К ним относятся:ORDEREDDISTINCTSORTEDSIZEDCONCURRENTIMMUTABLENONNULLSUBSIZED
.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