В одному з інтерв’ю мене запитали, яка перевага використання ітератора через for
цикл або яка перевага використання for
циклу над ітератором?
Чи може будь-який орган відповісти на це?
В одному з інтерв’ю мене запитали, яка перевага використання ітератора через for
цикл або яка перевага використання for
циклу над ітератором?
Чи може будь-який орган відповісти на це?
Iterator
?
Відповіді:
Перш за все, існує 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 з індексами з масивами, коли мені потрібен доступ до індексу всередині циклу.
Перевага ітератора:
next()
і previous()
.hasNext()
.Цикл був розроблений лише для ітерації над a Collection
, тому, якщо ви хочете просто виконати ітерацію над a Collection
, краще використовувати цикл типу for-Each
, але якщо ви хочете більше, ніж ви могли б використовувати Iterator.
ListIterator
також можна add
і почати ітерацію у довільній точці.
якщо ви отримуєте доступ до даних за номером (наприклад, "i"), це швидко, коли ви використовуєте масив. оскільки він безпосередньо надходить до елемента
Але для іншої структури даних (наприклад, дерева, списку) їй потрібно більше часу, оскільки вона починається від першого елемента до цільового елемента. при використанні списку. Йому потрібен час O (n). отже, це має бути повільно.
якщо ви використовуєте ітератор, компілятор знає, де ви знаходитесь. тому йому потрібен O (1) (оскільки він починається з поточного положення)
нарешті, якщо ви використовуєте лише масив або структуру даних, які підтримують прямий доступ (наприклад, список записів у java). "a [i]" - це добре. але, коли ви використовуєте іншу структуру даних, ітератор є більш ефективним
Collections
. Варто зазначити, що розширений цикл for використовує Iterator
нижню частину капота.
Основна відмінність між 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);
...
}
У вашому коді сказано: мені потрібно знати тип колекції, тому що мені потрібно переглядати її елементи певним чином, я також, можливо, збираюся перевірити наявність нулів або обчислити якийсь результат на основі порядку ітерацій. Що робить ваш код більш крихким, тому що якщо в будь-який момент зміниться тип колекції, це вплине на роботу вашого коду.
Підводячи підсумок, різниця полягає не стільки в швидкості чи використанні пам'яті, скільки в роз'єднанні коду, щоб він був більш гнучким, щоб справлятися зі змінами.
На відміну від інших відповідей, я хочу вказати на інші речі;
якщо вам потрібно виконати ітерацію більш ніж в одному місці вашого коду, ви, швидше за все, дублюєте логіку. Очевидно, це не надто розтяжний підхід. Натомість потрібен спосіб відокремити логіку вибору даних від коду, який їх фактично обробляє.
An итератор вирішує ці проблеми, надаючи загальний інтерфейс для циклу по набору даних , з тим , що основною структурою даних або механізм зберігання - такі , як array- прихований.
CopyOnWriteArrayList
але він добре відомий і часто використовується, тому про нього варто згадати.Це з книги, що це https://www.amazon.com/Beginning-Algorithms-Simon-Harris/dp/0764596748