1. Introduction
When we use for or while loops to traverse the elements of a collection, Iterator allows us not to worry about the location of the index, or even let us not only traverse a collection, but also change it.For example, if you want to delete elements in a loop, the for loop may not always work.
With custom iterators, we can iterate over more complex objects and move forward and backward, and it will become clear how to take advantage of them.
This article will discuss in depth how to use the Iterator and Iterable interfaces.
2. Iterator()
The Iterator interface is used to iterate over elements (List, Set, or Map) in a collection.It retrieves elements one by one and operates on each element as needed.
The following are the methods used to traverse collections and perform operations:
- .hasNext(): Returns true if the end of the collection has not been reached, false otherwise
- .next(): Returns the next element in the collection
- .remove(): Remove the last element returned by the iterator from the set
- .forEachRemaining(): Performs the given operation in order for each remaining element in the collection
First, since iterators are for collections, let's make a simple ArrayList with a few elements:
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");
We can iterate through this set using a simple loop:
System.out.println("Simple loop example:\n"); for (int i = 0; i < avengers.size(); i++) { System.out.println(avengers.get(i)); }
However, we want to explore iterators:
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()); }
What happens if we want to delete an element from this ArrayList?Let's try using the regular for loop:
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)); }
We'll get a nasty IndexOutOfBoundsException:
Simple loop example: Ant-Man Black Widow Captain America Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 3, Size: 3
This makes sense to change the size of a collection as it traverses, as does an enhanced for loop:
System.out.println("Simple loop example:\n"); for (String avenger : avengers) { if (avenger.equals("Doctor Strange")) { avengers.remove(avenger); } System.out.println(avenger); }
We received another exception again:
Simple loop example: Ant-Man Black Widow Captain America Doctor Strange Exception in thread "main" java.util.ConcurrentModificationException
Iterators come in handy, acting as intermediaries, removing elements from the collection, and ensuring that traversal continues as planned:
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(); } }
This is a safe way to ensure that elements are deleted when traversing a collection.
And confirm that the element has been deleted:
// 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);
The output is as follows:
For Each Remaining Example: Black Widow Captain America Doctor Strange
As you can see, the Ants have been removed from the Avengers Alliance list.
2.1. ListIterator()
ListIterator inherits from the Iterator interface.It is only used on Lists and can iterate in both directions, which means you can iterate from front to back or from back to front.It also does not have a current element, because the cursor is always placed between two elements of the List, so we use.previous() or.next() to access the element.
What is the difference between Iterator and ListIterator?
First, Iterator can be used for any collection -- List, Map, Queue, Set, and so on.
ListIterator can only be applied to Lists. By adding this restriction, ListIterator can be more specific in terms of methods, so we have introduced a number of new methods that can help us modify it as we traverse.
If you are working with List implementations (ArrayList, LinkedList, and so on), it is preferable to use ListIterator.
Here's what you might use:
- .add (E): Add elements to the List.
- .remove(): Remove the last element returned by.next() or.previous() from the List.
- .set(E): Overrides the last element returned by List.next() or.previous() with the specified element.
- .hasNext(): Returns true if the end of the List has not been reached, otherwise returns false.
- .next(): Returns the next element in the List.
- .nextIndex(): Returns the subscript of the next element.
- .hasPrevious(): Returns true if the beginning of the List has not been reached, otherwise returns false.
- .previous(): Returns the previous element of the List.
- .previousIndex(): Returns the subscript of the previous element.
Again, let's make an ArrayList of elements:
ArrayList<String> defenders = new ArrayList<>(); defenders.add("Daredevil"); defenders.add("Luke Cage"); defenders.add("Jessica Jones"); defenders.add("Iron Fist");
Let's use ListIterator to iterate through a List and print its elements:
ListIterator listIterator = defenders.listIterator(); System.out.println("Original contents of our List:\n"); while (listIterator.hasNext()) System.out.print(listIterator.next() + System.lineSeparator());
Obviously, it works the same way as Iterator.The output is as follows:
Original contents of our List: Daredevil Luke Cage Jessica Jones Iron Fist
Now let's try to modify some elements:
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); }
Now printing the List yields the following results:
Modified contents of our List: The Mighty Defender: Daredevil The Mighty Defender: Luke Cage The Mighty Defender: Jessica Jones The Mighty Defender: Iron Fist
Now let's iterate through the list upside down, as we can with ListIterator:
System.out.println("Modified List backwards:\n"); while (defendersListIterator.hasPrevious()) { System.out.println(defendersListIterator.previous()); }
The output is as follows:
Modified List backwards: The Mighty Defender: Iron Fist The Mighty Defender: Jessica Jones The Mighty Defender: Luke Cage The Mighty Defender: Daredevil
3. Spliterator()
The Spliterator interface is functionally the same as Iterator.You may never need to use Spliterator directly, but let's continue with some use cases.
But first you should be familiar with Java Streams and Lambda Expressions in Java.
Although we will list all methods owned by Spliterator, the entire work of the Spliterator interface is beyond the scope of this article.We will use an example to discuss how Spliterator can use parallelism to traverse treams that we can decompose more effectively.
The way we work with Spliterator is:
-
.characteristics()
: Returns what the Spliterator has as
int
The characteristics of the values.These include:
- ORDERED
- DISTINCT
- SORTED
- SIZED
- CONCURRENT
- IMMUTABLE
- NONNULL
- SUBSIZED
.estimateSize(): Returns an estimate of the number of elements traversed as long values, or long.MAX_VALUE if it cannot be returned.
.forEachRemaining (E): Performs a given operation on each remaining element in the collection in order.
.getComparator(): If the source of the Spliterator is sorted by Comparator, it returns Comparator.
.getExactSizeIfKnown(): Returns.estimateSize() if the size is known, or -1 if it is not.
.hasCharacteristics(int characteristics): Returns true if the.characteristics() of this Spliterator contains all the given features.
.tryAdvance (E): If there are remaining elements, perform the given operation on them, return true, otherwise return false.
.trySplit(): If this Spliterator can be partitioned, return a Spliterator override element, when returned from this method, it will not be overridden by this Spliterator.
As always, let's start with a simple 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");
Now we need to apply Spliterator to Stream.Fortunately, the Collections framework makes it easy to convert between ArrayList and Stream:
// Obtain a Stream to the mutants List. Stream<String> mutantStream = mutants.stream(); // Getting Spliterator object on mutantStream. Spliterator<String> mutantList = mutantStream.spliterator();
To show some of these methods, let's run them separately:
// .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));
We will get the output:
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
4. Iterable()
What if for some reason we want to create a custom Iterator interface?The first thing you need to be familiar with is this picture:
To create a custom Iterator, we need to make a custom implementation for.hasNext(),.next(), and.remove().
In the Iterator interface, there is a method that returns an iterator of elements in a collection, the.iterator() method, and a method that performs an operation for each element in the iterator, the.dorEach() method.
For example, suppose we're Tony Stark, we need to write a custom iterator to list each Iron Man suit in our current arsenal.
First, let's create a class to get and set suit data:
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; } }
Next let's write a custom Iterator:
// 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 } }
Finally, the main method class:
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); } } }
The output is as follows:
mark: 22, codename: HOTROD mark: 33, codename: SILVER CENTURION mark: 34, codename: SOUTHPAW mark: 48, codename: HULKBUSTER 2.0
5. Summary
In this article, we discussed in detail how to use iterators in Java, and even wrote a custom iterator to explore all the new possibilities of the Iterable interface.
We also discussed how Java leverages Stream's parallelism and uses the Spliterator interface to internally optimize traversal of collections.
August welfare arrived on time, pay attention to the public number
Background reply: 003 will receive the July translation collection oh~
Futures benefits reply: 001, 002 can be received!