1. Background on how iterators came to be

You are already familiar with HashSet. If you've really investigated it, beyond just reading a lesson, then you should have asked this question:

How do I display a list of all HashSet elements on the screen? After all, the interface does not have get() and set() methods!

And HashSet is not alone in this limitation. In addition to HashSet, there are many other collections that do not allow elements to be retrieved by index, because the elements have no defined order.

Over the years, programmers have invented lots of complex data structures, such as graphs and trees. Or lists of lists.

Many containers change the order of their elements when new elements are added or existing elements are removed. For example, a list stores elements in a particular order, and when a new element is added, it is almost always inserted in the middle of the list.

And we also get situations where there is a container that stores elements but not in any fixed order.

Now let's say we want to copy all the elements from such a collection into an array or list. We need to get all the elements. We don't care about the order in which we iterate over the elements — the important thing is not to iterate over the same elements more than once. How do we do that?


2. Iterator for a collection

Iterators were proposed as a solution to the problem above.

An iterator is a special object associated with a collection, which helps traverse all the elements of the collection without repeating any.

You can use the following code to get an iterator for any collection:

Iterator<Type> it = name.iterator();

Where name is the name of collection variable, Type is the type of the elements of the collection, iterator() is one of the collection's methods, and it is the name of the iterator variable.

An iterator object has 3 methods:

Method Description
Type next()
Returns the next element in the collection
boolean hasNext()
Checks whether there are any elements that have not been traversed yet
void remove()
Removes the current element of the collection

These methods are somewhat similar to the Scanner class's nextInt) and hasNextInt() methods.

The next() method returns the next element of the collection from which we got the iterator.

The hasNext() method checks whether the collection has additional elements that the iterator has not returned yet.

Here's how to display all the elements of a HashSet:

Code Notes
HashSet<String> set = new HashSet<String>();

set.add("Hallo");
set.add("Hello");
set.add("Hola");
set.add("Bonjour");
set.add("Ciao");
set.add("Namaste");

Iterator<String> it = set.iterator();
while (it.hasNext())
{
   String str = it.next();
   System.out.println(str);
}
Create a HashSet object that stores String elements.


We add greetings in various languages to the set variable.




Get an iterator object for the set set.
As long as there are still elements

Get the next element
Display the element on the screen


3. For-each loop

The main disadvantage of an iterator is that your code becomes more cumbersome than using a for loop.

To compare, let's display a list using a for loop and also using an iterator:

Iterator for loop
ArrayList<String> list = new ArrayList<String>();

Iterator<String> it = list.iterator();
while (it.hasNext())
{
   String str = it.next();
   System.out.println(str);
}
ArrayList<String> list = new ArrayList<String>();

for (int i = 0; i < list.size(); i++)
{
   String str = list.get(i);
   System.out.println(str);
}

Yes, it's much better to traverse the elements of an ArrayList using a loop — everything turns out to be shorter.

But Java's creators again decided to pour some sugar on us. Luckily for us, it was syntactic sugar.

They gave Java a new kind of loop and called it a for-each loop. This is how it looks in general:

for(Type name:collection)

Where collection is the name of the collection variable, Type is the type of the elements in the collection, and name is the name of a variable that takes the next value from the collection at each iteration of the loop.

This kind of loop iterates through all the elements of a collection using an implicit iterator. This is how it actually works:

For-each loop What the compiler sees: Loop with an iterator
ArrayList<String> list = new ArrayList<String>();

for (String str: list)
{
   System.out.println(str);
}
ArrayList<String> list = new ArrayList<String>();
Iterator<String> it = list.iterator();

while (it.hasNext())
{
   String str = it.next();
   System.out.println(str);
}

When the compiler encounters a for-each loop in your code, it simply replaces it with the code on the right: it adds a call to get an iterator along with any other missing method calls.

Programmers love the for-each loop and almost always use it when they need to iterate over all of the elements of a collection.

Even iterating over an ArrayList list using a for-each loop looks shorter:

For-each loop for loop
ArrayList<String> list = new ArrayList<String>();

for (String str: list)
{
   System.out.println(str);
}
ArrayList<String> list = new ArrayList<String>();

for (int i = 0; i < list.size(); i++)
{
   String str = list.get(i);
   System.out.println(str);
}


4. Removing an element in a for-each loop

The for-each loop has one drawback: it can't remove elements correctly. If you write code like this, you'll get an error.

Code Note
ArrayList<String> list = new ArrayList<String>();

list.add("Hallo");
list.add("Hello");
list.add("Hola");
list.add("Bonjour");
list.add("Ciao");
list.add("Namaste");

for (String str: list)
{
   if (str.equals("Hello"))
      list.remove(str);
}












The remove operation will generate an error!

This is a very nice and understandable code, but it won't work.

Important!

You can't change a collection while you are traversing it with an iterator.

There are three ways to get around this limitation.

1. Use a different kind of loop

When traversing an ArrayList collection, you can use an ordinary loop with an i counter variable.

Code
for (int i = 0; i < list.size(); i++)
{
   String str = list.get(i);

   if (str.equals("Hello"))
   {
      list.remove(str);
      i--; // We need to decrease i, because the remove operation shifted the elements
   }
}

However, this option is not suitable for HashSet and HashMap collections

2. Use an explicit iterator

You can use an iterator explicitly and call its remove() method.

Version that works Version that doesn't work
Iterator<String> it = set.iterator();
while (it.hasNext())
{
   String str = it.next();
   if (str.equals("Hello"))
       it.remove();
}

for (String str: list) { if (str.equals("Hello")) list.remove(str); }

Note that we call the remove() method on the iterator object! The iterator is aware that the item has been removed and can handle the situation correctly.

3. Use a copy of the collection

You can also create a copy of the collection and then use the copy in a for-each loop and delete elements from the original collection.

Code Note
ArrayList<String> listCopy = new ArrayList(list);

for (String str: listCopy)
{
   if (str.equals("Hello"))
      list.remove(str);
}
Creating a copy of a collection is super easy



The loop uses the iterator for the copy of the collection.
Elements are removed from the list collection.

The collection is copied rather quickly, since the elements themselves are not duplicated. Instead, the new collection stores references to the elements that already exist in the old collection.