Тъй като съм донякъде нов в езика 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'Stream API (вж. отговора на @i_am_zero's).)
В Java 8 класовете за колекции, които имплементират Iterable
(например всички List
s), вече имат метод forEach
, който може да се използва вместо демонстрирания по-горе for loop statement. (Тук има друг въпрос, който дава добро сравнение.)
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).
Какви други начини има, ако има такива?
(BTW, интересът ми изобщо не е свързан с желанието за оптимизиране на производителността; просто искам да знам какви форми са достъпни за мен като разработчик.)
Пример за всеки вид, посочен във въпроса:
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);
}
Споменавам ги, защото сте "донякъде нов в Java" и това може да е интересно.
Винаги можете да замените първия и третия пример с цикъл while и малко повече код. Това ви дава предимството да можете да използвате do-while:
int i = 0;
do{
E element = list.get(i);
i++;
}
while (i < list.size());
Разбира се, подобно нещо може да доведе до NullPointerException, ако list.size() върне 0, защото винаги се изпълнява поне веднъж. Това може да бъде поправено, като се провери дали елементът е null, преди да се използват неговите атрибути/методи. Все пак е много по-просто и лесно да се използва цикълът for