1. Предыстория появления итератора

Вы уже познакомились со множеством HashSet. И если вы действительно разбирались с ним, а не просто читали лекцию, должны были задаться вопросом:

А как вывести список всех элементов HashSet на экран? Ведь методов get() и set() у множества нет!

И HashSet в своей проблеме не одинок. Кроме HashSet, есть еще много различных коллекций, у которых нельзя получить элемент по номеру, ведь четкий порядок элементов отсутствует.

Программисты в свое время изобрели много сложных структур данных, таких как граф, дерево. Или, например, список списков.

Многие контейнеры меняют порядок своих элементов при добавлении новых или удалении существующих элементов. Например, есть список, который хранит элементы в отсортированном порядке, и при добавлении нового элемента он практически всегда вставляется в середину списка.

Вот мы и получаем ситуацию, что контейнер, содержащий элементы, есть, сами элементы тоже есть, а фиксированного порядка нет.

Но допустим, мы хотим скопировать все элементы из такой коллекции в массив или список. Нам нужно получить все элементы. Нам все равно в каком порядке мы их обойдем, главное — не повторяться. Как нам это сделать?


2. Итератор в коллекции

В качестве решения вышеописанной проблемы было предложено решение — итератор.

Итератор — это специальный объект у коллекции, который помогает обойти все элементы коллекции и не повторяться.

Получить итератор у любой коллекции можно с помощью кода:

Iterator<Тип> it = имя.iterator();

Где имя — это имя переменной-коллекции, Тип — это тип элементов коллекции. iterator() — это метод коллекции. it — это имя переменной-объекта-итератора.

У объекта-итератора есть 3 метода:

Метод Описание
Тип next()
Возвращает очередной элемент коллекции
boolean hasNext()
Проверяет, есть ли еще не пройденные элементы
void remove()
Удаляет текущий элемент коллекции

Эти методы чем-то похожи на методы класса Scanner: nextInt() и hasNextInt().

Метод next() возвращает очередной элемент коллекции, у которой мы получили итератор.

Метод hasNext() проверяет, есть ли еще элементы в коллекции, которые итератор не вернул.

Вот как можно вывести на экран все элементы множества HashSet:

Код Примечания
HashSet<String> set = new HashSet<String>();

set.add("Привет");
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);
}
Создаем объект типа HashSet, который хранит элементы типа String.


Заносим в set приветствия на разных языках.




Получаем объект-итератор у множества set.
Пока есть еще элементы

Получаем следующий элемент
Выводим элемент на экран

undefined
16
Задача
Java Syntax Pro, 16 уровень, 2 лекция
Недоступна
Перепутанные байты
Напиши программу, которая считывает с консоли путь к файлу1 и путь к файлу2. Далее все байты из файла1 записывает в файл2, но при этом меняет их местами по такому принципу: первый со вторым, третий с четвертым, и т.д. Если последний байт в файле1 нечетный, то пишем его в файл2 как есть. Для чтения и

3. Цикл for-each

Основной минус итератора в том, что с его использованием код получается еще более громоздким, чем с использованием цикла for.

Давайте для сравнения выведем на экран список с помощью цикла for и с помощью итератора:

Итератор Цикл for
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);
}

Да, элементы списка ArrayList гораздо лучше обходить с помощью цикла — все короче получается.

Однако разработчики Java вновь решили подсыпать нам сахарку. И на наше счастье, это опять был синтаксический сахар.

Они добавили в Java новый вид циклов и назвали его for-each. Вот как выглядит его использование в общем случае:

for(Тип имя:коллекция)

Где коллекция — это имя переменной коллекции, Тип — это тип элементов коллекции, а имя — это имя переменной, которая на каждом витке цикла принимает очередное значение из коллекции.

Этот цикл обходит все элементы коллекции с помощью скрытого итератора. Вот как он на самом деле работает:

Цикл for-each Что видит компилятор: Цикл с итератором
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);
}

Когда компилятор встретит в вашем коде цикл for-each, он просто заменит его на код справа: добавит метод получения итератора и все недостающие вызовы методов.

Программисты очень любят цикл for-each и практически всегда используют его, когда нужно обойти все элементы коллекции.

Даже обход списка ArrayList с помощью цикла for-each выглядит короче:

Цикл for-each Цикл for
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);
}

undefined
16
Задача
Java Syntax Pro, 16 уровень, 2 лекция
Недоступна
Что-то не копируется...
Есть программа, которая считывает из консоли путь к файлу1 и путь к файлу2 и копирует содержимое файла1 в файл2. Но она не работает корректно. Найди и исправь ошибки в коде, чтобы программа заработала как нужно.

4. Удаление элемента в цикле for-each

У цикла for-each есть один минус: он не умеет правильно удалять элементы. Если вы напишете такой код, получите ошибку.

Код Примечание
ArrayList<String> list = new ArrayList<String>();

list.add("Привет");
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);
}












При удалении возникнет ошибка!

Это очень красивый и понятный код, только работать он не будет.

Важно!

Нельзя менять коллекцию, пока вы обходите ее с помощью итератора.

Есть три способа обойти это ограничение.

1 Использование другого цикла

Если вы обходите коллекцию ArrayList, можете воспользоваться обычным циклом со счетчиком i.

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

   if (str.equals("Hello"))
   {
      list.remove(str);
      i--;    // нужно уменьшить i, т.к. после удаления элементы сдвинулись
   }
}

Однако этот вариант не подходит для коллекций HashSet и HashMap

2 Явное использование итератора

Можно использовать итератор явно и задействовать его метод remove().

Рабочий вариант Нерабочий вариант
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); }

Обратите внимание, что метод remove() мы вызываем у объекта-итератора! Итератор «будет знать» об удалении элемента и сможет правильно обработать эту ситуацию.

3 Использование копии коллекции

Также вы можете создать копию коллекции и использовать в цикле for-each коллекцию-копию, а удалять элементы из оригинальной коллекции.

Код Примечание
ArrayList<String> listCopy = new ArrayList(list);

for (String str: listCopy)
{
   if (str.equals("Hello"))
      list.remove(str);
}
Копию коллекции создать очень легко



Цикл использует итератор коллекции-копии.
Элементы удаляются из коллекции list.

Копия коллекции создается довольно быстро: элементы при копировании коллекции не дублируются, в новой коллекции будут храниться ссылки на те же элементы, что и в старой.

undefined
16
Задача
Java Syntax Pro, 16 уровень, 2 лекция
Недоступна
Фейсконтроль
Напиши программу, которая считывает из консоли имя текстового файла, далее читает символы из этого файла (используй метод readAllLines(Path) класса Files) и выводит на экран все, за исключением точки, запятой и пробела.