1. Предистория на това How са се появor итераторите

Вече сте запознати с HashSet. Ако наистина сте го изследвали, освен просто да прочетете урок, тогава трябваше да зададете този въпрос:

Как да покажа списък с всички елементи на HashSet на екрана? В крайна сметка интерфейсът няма get()и set()методи!

И HashSetне е сам в това ограничение. В допълнение към HashSet, има много други колекции, които не позволяват елементите да бъдат извличани по индекс, тъй като елементите нямат определен ред.

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

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

И също така получаваме ситуации, в които има контейнер, който съхранява елементи, но не във фиксиран ред.

Сега да кажем, че искаме да копираме всички елементи от такава колекция в масив or списък. Трябва да вземем всички елементи. Ние не се интересуваме от реда, в който итерираме елементите - важното е да не итерирате едни и същи елементи повече от веднъж. Как да направим това?


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

Итераторите бяха предложени като решение на проблема по-горе.

Итераторът е специален обект, свързан с колекция, който помага да се премине през всички елементи на колекцията, без да се повтаря нито един.

Можете да използвате следния code, за да получите итератор за всяка колекция:

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

Къде nameе името на променливата на колекцията, Typeе типът на елементите на колекцията, iterator()е един от методите на колекцията и itе името на променливата на итератора.

Итераторният обект има 3 метода:

Метод Описание
Type next()
Връща следващия елемент в колекцията
boolean hasNext()
Проверява дали има елементи, които все още не са обходени
void remove()
Премахва текущия елемент от колекцията

Тези методи са донякъде подобни на класа nextInt)и hasNextInt()методите на Scanner.

Методът next()връща следващия елемент от колекцията, от която сме получor итератора.

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

Ето How да покажете всички елементи на HashSet:

Код Бележки
HashSet<String> set = new HashSet<String>();

set.add("Hello");
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набора.
Докато все още има елементи

Вземете следващия елемент
Показване на елемента на екрана


3. For-eachцикъл

Основният недостатък на итератора е, че вашият code става по-тромав от използването на 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. Ето How изглежда най-общо:

for(Type name:collection)

Къде collectionе името на променливата на колекцията, Typeе типът на елементите в колекцията и nameе името на променлива, която приема следващата стойност от колекцията при всяка итерация на цикъла.

Този вид цикъл итерира през всички елементи на колекция, използвайки имплицитен итератор. Ето How всъщност работи:

За всеки цикъл Какво вижда компилаторът: Цикъл с итератор
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цикъл във вашия code, той просто го замества с codeа отдясно: добавя извикване за получаване на итератор заедно с всички други липсващи извиквания на метод.

Програмистите обичат for-eachцикъла и почти винаги го използват, когато трябва да повторят всички елементи на колекция.

Дори повторението върху ArrayListсписък с помощта на for-eachцикъл изглежда по-кратко:

За всеки цикъл за цикъл
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. Премахване на елемент в for-eachцикъл

Цикълът for-eachима един недостатък: не може да премахва правилно елементи. Ако пишете code по този начин, ще получите грешка.

Код Забележка
ArrayList<String> list = new ArrayList<String>();

list.add("Hello");
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);
}












Операцията за премахване ще генерира грешка!

Това е много хубав и разбираем code, но няма да работи.

важно!

Не можете да промените колекция, докато я обхождате с итератор.

Има три начина да заобиколите това ограничение.

1. Използвайте различен вид цикъл

When traversing an ArrayList collection, можете да използвате обикновен цикъл с iпроменлива брояч.

Код
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
   }
}

Тази опция обаче не е подходяща за 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колекцията.

Колекцията се копира доста бързо, тъй като самите елементи не се дублират. Вместо това новата колекция съхранява препратки към елементите, които вече съществуват в старата колекция.