Ітератор проти


75

В одному з інтерв’ю мене запитали, яка перевага використання ітератора через forцикл або яка перевага використання forциклу над ітератором?

Чи може будь-який орган відповісти на це?


2
Для кожного циклу використовується ітератор під кришками. Різниця полягає лише в читабельності (і, отже, у ремонтопридатності).
Dawood ibn Kareem

1
Ну; чому ти сказав Iterator?
Борис Павук

3
Ніщо не змушує вас негайно відповісти. Ви можете міркувати про різні види колекцій, те, що кожен код робить за кадром, і демонструвати свою здатність міркувати, навіть якщо ви не можете сформулювати остаточну відповідь. Ось що важливо: вміти думати. Я б розпочав свою відповідь з: "ну, я не знаю. Думаю, це залежить від того, що ви хочете зробити, і від того, який тип колекції. Повинна бути вагома причина використовувати обидва, але це повинно залежати від Давайте уявимо, що відбувається з ArrayList. Потім з LinkedList. тощо. тощо "
JB Nizet

3
Я повністю згоден з @JBNizet. Сказати, що я не знаю цього набагато краще, ніж вибрати випадковий варіант - це створює у мене враження, що ви зробите це під час програмування; вибирайте перше, що спадає на думку, не замислюючись. Ви можете спробувати пояснити свої знання в цій галузі, ніж сказати, що ви недостатньо знаєте, щоб дати обґрунтовану відповідь.
Борис Павук

2
Обидві поточні відповіді неповні. Перш за все, існує 2 види циклів for, які поводяться дуже по-різному. Один використовує індекси (що не завжди можливо), а інший використовує ітератор за лаштунками. Отже, насправді у вас є 3 петлі для порівняння. Тоді ви можете порівняти їх різними термінами: продуктивність, читабельність, схильність до помилок, можливості. Ітератор може робити те, чого не може цикл foreach. Але ітератор є більш небезпечним і менш читабельним. А використання індексів для доступу до елементів можливо лише зі Списками, але не ефективно, якщо це пов’язаний список. Тож не існує «найкращого» способу.
JB Nizet

Відповіді:


130

Перш за все, існує 2 види циклів for, які поводяться дуже по-різному. Використовуються індекси:

for (int i = 0; i < list.size(); i++) {
    Thing t = list.get(i);
    ...
}

Цей тип циклу не завжди можливий. Наприклад, у Списках є індекси, а в Наборах немає, оскільки це невпорядковані колекції.

Інший, цикл foreach, використовує ітератор за кадром:

for (Thing thing : list) {
    ...
}

Це працює з усіма видами колекцій Iterable (або масивів)

І нарешті, ви можете використовувати ітератор, який також працює з будь-яким Iterable:

for (Iterator<Thing> it = list.iterator(); it.hasNext(); ) {
    Thing t = it.next();
    ...
} 

Отже, насправді у вас є 3 петлі для порівняння.

Ви можете порівняти їх різними термінами: продуктивність, читабельність, схильність до помилок, можливості.

Ітератор може робити те, чого не може цикл foreach. Наприклад, ви можете видаляти елементи під час ітерації, якщо ітератор це підтримує:

for (Iterator<Thing> it = list.iterator(); it.hasNext(); ) {
    Thing t = it.next();
    if (shouldBeDeleted(thing) {
        it.remove();
    }
} 

Списки також пропонують ітератори, які можуть повторювати в обох напрямках. Цикл foreach повторюється лише від початку до кінця.

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

for (Iterator<Thing> it = list.iterator(); it.hasNext(); ) {
    System.out.println(it.next().getFoo());
    System.out.println(it.next().getBar());
} 

Цикл foreach не дозволяє такої помилки.

Використання індексів для доступу до елементів трохи ефективніше із колекціями, підкріпленими масивом. Але якщо ви передумаєте і скористаєтеся LinkedList замість ArrayList, раптом продуктивність буде жахливою, тому що кожного разу, коли ви отримуєте доступ list.get(i), зв'язаний список повинен буде циклічно переглядати всі його елементи до i-го. Ітератор (і, отже, цикл foreach) не має цієї проблеми. Він завжди використовує найкращий можливий спосіб ітерації елементів даної колекції, оскільки сама колекція має власну реалізацію Iterator.

Моє загальне емпіричне правило: використовуйте цикл foreach, якщо вам дійсно не потрібні можливості ітератора. Я б використовував лише цикл for з індексами з масивами, коли мені потрібен доступ до індексу всередині циклу.


Коли ви використовуєте ітератор для видалення одного або кількох елементів у колекції, це безпечно зробити. Коли ви використовуєте цикл for-for або for, видалення або заборонено, або викликає помилку, оскільки під час видалення під час циклу по колекції змінюються індекси кожного елемента в колекції. Тому ви повинні використовувати зворотний цикл for або використовувати ітератор, щоб запобігти появі помилки. Дивіться також цю відповідь .
user504342 05

2
те, що ви сказали "ith" - це виклик англійською мовою.
MDP

1
Може, він може замінити його на "i-й"?
Мирослав Тодоров

17

Перевага ітератора:

  • Можливість видалення елементів із колекцій.
  • Можливість переміщення вперед і назад за допомогою next()і previous().
  • Можливість перевірити, чи є більше елементів чи ні, за допомогою hasNext().

Цикл був розроблений лише для ітерації над a Collection, тому, якщо ви хочете просто виконати ітерацію над a Collection, краще використовувати цикл типу for-Each, але якщо ви хочете більше, ніж ви могли б використовувати Iterator.


1
А за допомогою a ListIteratorтакож можна addі почати ітерацію у довільній точці.
Борис Павук

@Salah: чому ітератор може видаляти елементи під час ітерації колекції?
logbasex

12

якщо ви отримуєте доступ до даних за номером (наприклад, "i"), це швидко, коли ви використовуєте масив. оскільки він безпосередньо надходить до елемента

Але для іншої структури даних (наприклад, дерева, списку) їй потрібно більше часу, оскільки вона починається від першого елемента до цільового елемента. при використанні списку. Йому потрібен час O (n). отже, це має бути повільно.

якщо ви використовуєте ітератор, компілятор знає, де ви знаходитесь. тому йому потрібен O ​​(1) (оскільки він починається з поточного положення)

нарешті, якщо ви використовуєте лише масив або структуру даних, які підтримують прямий доступ (наприклад, список записів у java). "a [i]" - це добре. але, коли ви використовуєте іншу структуру даних, ітератор є більш ефективним


Тож я повинен сказати, що це залежить від вимог.
розгойдування

+1 за вказівку на недоліки використання циклу за індексом на Java Collections. Варто зазначити, що розширений цикл for використовує Iteratorнижню частину капота.
Борис Павук

Ваші інші приклади структури даних не корисні - ArrayList забезпечує індексований доступ. Можливо, оновити, щоб включити певні колекції Java? (наприклад, ArrayList vs LinkedList)
Річард Міскін,

11

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

Коли ваш код використовує ітератор, або у цій формі

for(Item element : myCollection) { ... }

ця форма

Iterator<Item> iterator = myCollection.iterator();
while(iterator.hasNext()) {    
    Item element = iterator.next();    
    ... 
}

або ця форма

for(Iterator iterator = myCollection.iterator(); iterator.hasNext(); ) {
   Item element = iterator.next();
   ...
}

Ваш код говорить: "Мені все одно тип колекції та її реалізація, я просто дбаю про те, щоб я міг переглядати її елементи". Що зазвичай є кращим підходом, оскільки це робить ваш код більш розв’язаним.

З іншого боку, якщо ви використовуєте класичний цикл for, як у

for(int i = 0; i < myCollection.size(); i++) {
   Item element = myCollection.get(i);
   ...
}

У вашому коді сказано: мені потрібно знати тип колекції, тому що мені потрібно переглядати її елементи певним чином, я також, можливо, збираюся перевірити наявність нулів або обчислити якийсь результат на основі порядку ітерацій. Що робить ваш код більш крихким, тому що якщо в будь-який момент зміниться тип колекції, це вплине на роботу вашого коду.

Підводячи підсумок, різниця полягає не стільки в швидкості чи використанні пам'яті, скільки в роз'єднанні коду, щоб він був більш гнучким, щоб справлятися зі змінами.


2

На відміну від інших відповідей, я хочу вказати на інші речі;

якщо вам потрібно виконати ітерацію більш ніж в одному місці вашого коду, ви, швидше за все, дублюєте логіку. Очевидно, це не надто розтяжний підхід. Натомість потрібен спосіб відокремити логіку вибору даних від коду, який їх фактично обробляє.

An итератор вирішує ці проблеми, надаючи загальний інтерфейс для циклу по набору даних , з тим , що основною структурою даних або механізм зберігання - такі , як array- прихований.

  • Ітератор - це поняття, а не реалізація.
  • Ітератор забезпечує ряд операцій для обходу та доступу до даних.
  • Ітератор може обернути будь-яку структуру даних, наприклад масив.
  • Однією з найбільш цікавих та корисних переваг використання ітераторів є можливість обернути або прикрасити інший ітератор для фільтрації повернутих значень
  • Ітератор може бути безпечним для потоку, тоді як цикл for сам по собі не може бути, оскільки він безпосередньо звертається до елементів. Єдиним популярним ітератором захисту потоків є, CopyOnWriteArrayListале він добре відомий і часто використовується, тому про нього варто згадати.

Це з книги, що це https://www.amazon.com/Beginning-Algorithms-Simon-Harris/dp/0764596748


0

Я наткнувся на це питання. Відповідь лежить на проблемах, які намагається вирішити Ітератор:

  • отримувати доступ та обходити елементи сукупного об’єкта, не виставляючи його представлення
  • визначити операції обходу для сукупного об’єкта без зміни його інтерфейсу
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.