JavaRush /Курси /Java Syntax Zero /Цикл for-each

Цикл for-each

Java Syntax Zero
Рівень 14 , Лекція 2
Відкрита

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

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


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);
}


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.

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

Коментарі (18)
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ
Олег Рівень 117
14 жовтня 2025
!!! Остання задача - Під час пошуку слова bug регістр не враховується.
Christopher Ward 595 Рівень 16
8 серпня 2024
Колись я навчусь уважно читати умову задачі. Знову 30 хв. витратив щоб розібратись чого валідатор не зараховує звдачу.
Antek_Ukraine Рівень 14 Expert
17 липня 2023
Цікава задачка з пошуком bug.ignoreCase :)))
Андрій Рівень 18
22 січня 2024
Дякую :)
25 травня 2023
-чому ти не використовуєш for-each? -У мене цукровий діабет.
les_yeux_blancs Рівень 50
26 квітня 2023
гайз, ну шо за фігня) чому валідатор не приймає правильні рішення, які не суперечать умові? треба або чіткіше формулювати вимоги, або перевіряти лише результат, бо код знизу працює, але не проходить валідацію

var iterator = words.iterator();
iterator.forEachRemaining(System.out::println);
Ось умови: 1. Клас Solution має містити публічний статичний метод print(HashSet<String>), який не повертає жодного значення (тип void). 2. Метод print(HashSet<String>) має працювати згідно з умовою.
Oleksandr Tkachenko Рівень 51
18 квітня 2023
Так можна код писати і вирішувати задачі до скінчення віків)))
Yevhenii Рівень 17
9 березня 2023
Зрозуміла лекція і завдання відповідають поданій інформації
kivi Рівень 20
27 лютого 2023
нас вчили що для циклу for є шорткат в IntelliJ IDEA -

fori <tab>
для циклу for-each є також шорткат -

fore <tab>
Yaroslav Tkachyk Рівень 23 Expert
6 січня 2023
Чи є відмінність між рядками коду? ArrayList <String> copy = new ArrayList (list); ArrayList <String> copy = (ArrayList <String>) list.clone(); і чому в прикладі після new ArrayList відстутнє вказання <Типу елементів, які зберігаються в колекції>
les_yeux_blancs Рівень 50
26 квітня 2023
1. Та в принципі немає, якщо копнути в сорс код, то можна побачити, що перший варіант в конструкторі просто робить list.toArray() і зберігає цей масив у себе, а другий - викликає метод clone у Object і сетить в його внутрішній масив копію свого (дуже рекомендую робити Control/Cmd + click на методі й намагатися глянути у сорси, там іноді усе досить просто) ) 2. Тому що компілятор розумний і, розпарсивши першу частину виразу (ArrayList <String> copy), він вже знає, який тип даних цей список зберігає. Якщо користуєшся IDEA, то вона підсвітить тобі явно вказаний тип і запропонує прибрати його, Але там має бути запис ArrayList<>. У цьому ж випадку отримують список без типу и пхають його у змінну, що виділила памʼять для списку, що зберігає рядки, IDEA у мене на це кричить
Pan Vitali Moroz Рівень 51
4 жовтня 2022
ну якщо копіюються не елементи а посилання то так, можливо краще використати for each, буде більше зрозуміліший код. На мій погляд щось створювати і потім копіювати аби видалити - виглядае сумнівно, якщо вже існуе ітератор З іншоі сторони можливо безпечніше використовувати у коді копію а не оригінал це вже аргумент плюс зрозуміліше подання - переміг for each!