Итерируемый интерфейс Java: итератор, ListIterator и Spliterator

Введение Хотя мы можем использовать цикл for или while для обхода коллекции элементов, Iterator позволяет нам делать это, не беспокоясь о позициях индекса, и даже позволяет нам не только просматривать коллекцию, но и изменять ее одновременно. , что не всегда возможно с циклами for, например, если вы удаляете элементы в цикле. Добавьте к этому возможность реализовать наш настраиваемый Iterator для итерации по гораздо более сложным объектам, а также для продвижения вперед и назад.

Вступление

В то время как мы можем использовать 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

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus