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

Навіть обхід списку 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);
}


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)
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ
Олег Рівень 21
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!