Цикл for-each

Відкрита

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.
Поки ще є непройдені елементи

Отримуємо наступний елемент
Виводимо елемент на екран

14
Задача
Java Syntax Zero,  14 рівень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 і практично завжди використовують його, коли потрібно обійти всі елементи колекції.

З використанням циклу for-each навіть обхід списку ArrayList буде коротшим:

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

14
Задача
Java Syntax Zero,  14 рівень2 лекція
Недоступна
Перетворюємо ітератор на цикл for-each
Перетворюємо ітератор на цикл for-each

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.

Копія колекції створюється досить швидко: під час копіювання елементи колекції не дублюються — у новій колекції зберігатимуться посилання на ті самі елементи, що й у старій.

14
Задача
Java Syntax Zero,  14 рівень2 лекція
Недоступна
Знайти і знешкодити
Знайти і знешкодити
Коментарі (18)
  • популярні
  • нові
  • старі
Щоб залишити коментар, потрібно ввійти в систему
Олег
Рівень 117
14 жовтня 2025, 08:47
!!! Остання задача - Під час пошуку слова bug регістр не враховується.
Christopher Ward 595
Рівень 16
8 серпня 2024, 13:06
Колись я навчусь уважно читати умову задачі. Знову 30 хв. витратив щоб розібратись чого валідатор не зараховує звдачу.
Antek_Ukraine
Рівень 14
Expert
17 липня 2023, 17:47
Цікава задачка з пошуком bug.ignoreCase :)))
Андрій
Рівень 18
22 січня 2024, 15:57
Дякую :)
25 травня 2023, 11:06
-чому ти не використовуєш for-each? -У мене цукровий діабет.
les_yeux_blancs QA Automation Engineer
26 квітня 2023, 20:10
гайз, ну шо за фігня) чому валідатор не приймає правильні рішення, які не суперечать умові? треба або чіткіше формулювати вимоги, або перевіряти лише результат, бо код знизу працює, але не проходить валідацію
var iterator = words.iterator();
iterator.forEachRemaining(System.out::println);
Ось умови: 1. Клас Solution має містити публічний статичний метод print(HashSet<String>), який не повертає жодного значення (тип void). 2. Метод print(HashSet<String>) має працювати згідно з умовою.
Oleksandr Tkachenko
Рівень 51
18 квітня 2023, 13:35
Так можна код писати і вирішувати задачі до скінчення віків)))
Yevhenii
Рівень 17
9 березня 2023, 19:12
Зрозуміла лекція і завдання відповідають поданій інформації
kivi
Рівень 20
27 лютого 2023, 18:05
нас вчили що для циклу for є шорткат в IntelliJ IDEA -
fori <tab>
для циклу for-each є також шорткат -
fore <tab>
Yaroslav Tkachyk
Рівень 23
Expert
6 січня 2023, 15:52
Чи є відмінність між рядками коду? ArrayList <String> copy = new ArrayList (list); ArrayList <String> copy = (ArrayList <String>) list.clone(); і чому в прикладі після new ArrayList відстутнє вказання <Типу елементів, які зберігаються в колекції>
les_yeux_blancs QA Automation Engineer
26 квітня 2023, 20:31
1. Та в принципі немає, якщо копнути в сорс код, то можна побачити, що перший варіант в конструкторі просто робить list.toArray() і зберігає цей масив у себе, а другий - викликає метод clone у Object і сетить в його внутрішній масив копію свого (дуже рекомендую робити Control/Cmd + click на методі й намагатися глянути у сорси, там іноді усе досить просто) ) 2. Тому що компілятор розумний і, розпарсивши першу частину виразу (ArrayList <String> copy), він вже знає, який тип даних цей список зберігає. Якщо користуєшся IDEA, то вона підсвітить тобі явно вказаний тип і запропонує прибрати його, Але там має бути запис ArrayList<>. У цьому ж випадку отримують список без типу и пхають його у змінну, що виділила памʼять для списку, що зберігає рядки, IDEA у мене на це кричить
Pan Vitali Moroz
Рівень 51
4 жовтня 2022, 11:20
ну якщо копіюються не елементи а посилання то так, можливо краще використати for each, буде більше зрозуміліший код. На мій погляд щось створювати і потім копіювати аби видалити - виглядае сумнівно, якщо вже існуе ітератор З іншоі сторони можливо безпечніше використовувати у коді копію а не оригінал це вже аргумент плюс зрозуміліше подання - переміг for each!