Будучи дещо новачком у мові Java, я намагаюся ознайомитися з усіма способами (або, принаймні, непатологічними), якими можна ітерувати список (або, можливо, інші колекції), а також з перевагами чи недоліками кожного з них.
Маючи об'єкт List
, я знаю наступні способи перебору всіх елементів:
while
/ do while
)// Not recommended (see below)!
for (int i = 0; i < list.size(); i++) {
E element = list.get(i);
// 1 - can call methods of element
// 2 - can use 'i' to make index-based calls to methods of list
// ...
}
Примітка: Як зазначив @amarseillan, ця форма є поганим вибором
для ітерації над List
, тому що фактична реалізація
методу get
може бути не такою ефективною, як при використанні Iterator
.
Наприклад, реалізація LinkedList
повинна перебрати всі
елементи, що передують i, щоб отримати i-ий елемент.
У вищенаведеному прикладі немає можливості для реалізації List
зберегти своє місце
"зберегти своє місце", щоб зробити майбутні ітерації більш ефективними.
Для ArrayList
це не має особливого значення, оскільки складність/вартість get
є постійною за часом (O(1)), в той час як для LinkedList
вона пропорційна розміру списку (O(n)).
Для отримання додаткової інформації про обчислювальну складність вбудованих реалізацій Collections
ознайомтеся з цим питанням.
for (E element : list) {
// 1 - can call methods of element
// ...
}
for (Iterator<E> iter = list.iterator(); iter.hasNext(); ) {
E element = iter.next();
// 1 - can call methods of element
// 2 - can use iter.remove() to remove the current element from the list
// ...
}
for (ListIterator<E> iter = list.listIterator(); iter.hasNext(); ) {
E element = iter.next();
// 1 - can call methods of element
// 2 - can use iter.remove() to remove the current element from the list
// 3 - can use iter.add(...) to insert a new element into the list
// between element and iter->next()
// 4 - can use iter.set(...) to replace the current element
// ...
}
list.stream().map(e -> e + 1); // Can apply a transformation function for e
(Метод відображення з Java 8's Stream API (див. відповідь @i_am_zero)).
В Java 8 класи колекцій, що реалізують Iterable
(наприклад, всі List
) тепер мають метод forEach
, який можна використовувати замість продемонстрованого вище оператора циклу for. (Ось інше питання, яке надає гарне порівняння).
Arrays.asList(1,2,3,4).forEach(System.out::println);
// 1 - can call methods of an element
// 2 - would need reference to containing object to remove an item
// (TODO: someone please confirm / deny this)
// 3 - functionally separates iteration from the action
// being performed with each item.
Arrays.asList(1,2,3,4).stream().forEach(System.out::println);
// Same capabilities as above plus potentially greater
// utilization of parallelism
// (caution: consequently, order of execution is not guaranteed,
// see [Stream.forEachOrdered][stream-foreach-ordered] for more
// information about this).
Які інші способи існують, якщо такі є?
(До речі, мій інтерес зовсім не пов'язаний з бажанням [оптимізувати продуктивність][ітератор-продуктивність-питання]; я просто хочу знати, які форми доступні мені як розробнику).
Приклад кожного з видів, перелічених у запитанні:
import java.util.*;
public class ListIterationExample {
public static void main(String []args){
List<Integer> numbers = new ArrayList<Integer>();
// populates list with initial values
for (Integer i : Arrays.asList(0,1,2,3,4,5,6,7))
numbers.add(i);
printList(numbers); // 0,1,2,3,4,5,6,7
// replaces each element with twice its value
for (int index=0; index < numbers.size(); index++) {
numbers.set(index, numbers.get(index)*2);
}
printList(numbers); // 0,2,4,6,8,10,12,14
// does nothing because list is not being changed
for (Integer number : numbers) {
number++; // number = new Integer(number+1);
}
printList(numbers); // 0,2,4,6,8,10,12,14
// same as above -- just different syntax
for (Iterator<Integer> iter = numbers.iterator(); iter.hasNext(); ) {
Integer number = iter.next();
number++;
}
printList(numbers); // 0,2,4,6,8,10,12,14
// ListIterator<?> provides an "add" method to insert elements
// between the current element and the cursor
for (ListIterator<Integer> iter = numbers.listIterator(); iter.hasNext(); ) {
Integer number = iter.next();
iter.add(number+1); // insert a number right before this
}
printList(numbers); // 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
// Iterator<?> provides a "remove" method to delete elements
// between the current element and the cursor
for (Iterator<Integer> iter = numbers.iterator(); iter.hasNext(); ) {
Integer number = iter.next();
if (number % 2 == 0) // if number is even
iter.remove(); // remove it from the collection
}
printList(numbers); // 1,3,5,7,9,11,13,15
// ListIterator<?> provides a "set" method to replace elements
for (ListIterator<Integer> iter = numbers.listIterator(); iter.hasNext(); ) {
Integer number = iter.next();
iter.set(number/2); // divide each element by 2
}
printList(numbers); // 0,1,2,3,4,5,6,7
}
public static void printList(List<Integer> numbers) {
StringBuilder sb = new StringBuilder();
for (Integer number : numbers) {
sb.append(number);
sb.append(",");
}
sb.deleteCharAt(sb.length()-1); // remove trailing comma
System.out.println(sb.toString());
}
}
Я не знаю, що ви вважаєте патологією, але дозвольте мені запропонувати деякі альтернативи, які ви могли не бачити раніше:
List<E> sl= list ;
while( ! sl.empty() ) {
E element= sl.get(0) ;
.....
sl= sl.subList(1,sl.size());
}
Або його рекурсивна версія:
void visit(List<E> list) {
if( list.isEmpty() ) return;
E element= list.get(0) ;
....
visit(list.subList(1,list.size()));
}
Також, рекурсивна версія класичного for(int i=0...
:
void visit(List<E> list,int pos) {
if( pos >= list.size() ) return;
E element= list.get(pos) ;
....
visit(list,pos+1);
}
Я згадую про них тому, що Ви &quo ;дещо новачок в Java&quo ; і це може бути цікаво.
Ви завжди можете замінити перший і третій приклади за допомогою циклу while і трохи більше коду. Це дасть вам перевагу у використанні циклу do-while:
int i = 0;
do{
E element = list.get(i);
i++;
}
while (i < list.size());
Звичайно, подібні речі можуть викликати виключення NullPointerException, якщо list.size() повертає 0, тому що він завжди виконується хоча б один раз. Це можна виправити, перевіривши, чи елемент є нульовим перед використанням його атрибутів/методів tho. Але, все ж таки, набагато простіше і зручніше використовувати цикл for