Các cách lặp lại qua một danh sách trong Java


576

Là một phần mới đối với ngôn ngữ Java Tôi đang cố gắng làm quen với tất cả các cách (hoặc ít nhất là những cách không bệnh lý) mà người ta có thể lặp lại qua một danh sách (hoặc có lẽ các bộ sưu tập khác) và những ưu điểm hoặc nhược điểm của mỗi cách.

Đưa ra một List<E> listđối tượng, tôi biết các cách sau để lặp qua tất cả các yếu tố:

Cơ bản cho vòng lặp (tất nhiên, cũng có tương đương while/ do whilevòng lặp)

// 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

    // ...
}

Lưu ý: Như @amarseillan đã chỉ ra, hình thức này là một lựa chọn kém cho việc lặp lại qua Lists, bởi vì việc triển khai thực tế của getphương pháp có thể không hiệu quả như khi sử dụng Iterator. Ví dụ, LinkedListviệc triển khai phải duyệt qua tất cả các phần tử trước i để có phần tử thứ i.

Trong ví dụ trên, không có cách nào để Listtriển khai "lưu vị trí của nó" để làm cho các lần lặp lại trong tương lai hiệu quả hơn. Đối với một ArrayListđiều đó không thực sự quan trọng, bởi vì độ phức tạp / chi phí getlà thời gian không đổi (O (1)) trong khi đối với a LinkedListlà tỷ lệ với kích thước của danh sách (O (n)).

Để biết thêm thông tin về độ phức tạp tính toán của các Collectionstriển khai tích hợp, hãy xem câu hỏi này .

Tăng cường cho vòng lặp (giải thích độc đáo trong câu hỏi này )

for (E element : list) {
    // 1 - can call methods of element

    // ...
}

Lặp lại

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

    // ...
}

ListIterator

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

    // ...
}

Chức năng Java

list.stream().map(e -> e + 1); // Can apply a transformation function for e

Iterable.forEach , Stream.forEach , ...

(Một phương pháp bản đồ từ API Stream của Java 8 (xem câu trả lời @ i_am_zero).)

Trong các lớp tập hợp Java 8 triển khai Iterable(ví dụ, tất cả các Lists) hiện có một forEachphương thức, có thể được sử dụng thay cho câu lệnh for loop được trình bày ở trên. (Đây là một câu hỏi khác cung cấp một so sánh tốt.)

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).

Những cách khác là có, nếu có?

(BTW, mối quan tâm của tôi hoàn toàn không xuất phát từ mong muốn tối ưu hóa hiệu suất ; tôi chỉ muốn biết những hình thức nào có sẵn cho tôi với tư cách là nhà phát triển.)


1
Đó là những cái không bệnh lý, mặc dù bạn cũng có thể sử dụng bất kỳ thư viện kiểu chức năng nào để xử lý các bộ sưu tập.
Dave Newton

Là câu hỏi về Listgiao diện cụ thể?
Sotirios Delimanolis

@SotiriosDelimanolis, đối với tất cả ý định và mục đích, vâng, nó đặc trưng cho <code> Danh sách </ code>, nhưng nếu có những cách thú vị khác để làm việc, giả sử, <code> Bộ sưu tập </ code>, tôi sẽ quan tâm để biết họ.
iX3

@DaveNewton, cảm ơn vì ý tưởng. Tôi chưa bao giờ sử dụng bất cứ thứ gì như vậy. Xin hãy xem câu hỏi đã được chỉnh sửa của tôi và cho tôi biết nếu tôi hiểu ý của bạn.
iX3

2
@sdasdadas, đã hoàn thành: stackoverflow.com/questions/18410035/
Kẻ

Câu trả lời:


265

Ba hình thức lặp gần giống nhau. forVòng lặp nâng cao :

for (E element : list) {
    . . .
}

là, theo Đặc tả ngôn ngữ Java , có hiệu lực giống hệt với việc sử dụng rõ ràng một trình vòng lặp với một forvòng lặp truyền thống . Trong trường hợp thứ ba, bạn chỉ có thể sửa đổi nội dung danh sách bằng cách xóa phần tử hiện tại và sau đó, chỉ khi bạn thực hiện thông qua removephương thức của chính trình lặp. Với phép lặp dựa trên chỉ mục, bạn có thể tự do sửa đổi danh sách theo bất kỳ cách nào. Tuy nhiên, việc thêm hoặc xóa các phần tử xuất hiện trước các chỉ số hiện tại có nguy cơ bỏ qua các phần tử vòng lặp của bạn hoặc xử lý cùng một phần tử nhiều lần; bạn cần điều chỉnh chỉ số vòng lặp đúng khi bạn thực hiện những thay đổi đó.

Trong mọi trường hợp, elementlà một tham chiếu đến thành phần danh sách thực tế. Không có phương pháp lặp nào tạo ra một bản sao của bất cứ thứ gì trong danh sách. Thay đổi trạng thái nội bộ elementsẽ luôn được nhìn thấy trong trạng thái bên trong của phần tử tương ứng trong danh sách.

Về cơ bản, chỉ có hai cách để lặp qua một danh sách: bằng cách sử dụng một chỉ mục hoặc bằng cách sử dụng một trình vòng lặp. Vòng lặp for được tăng cường chỉ là một lối tắt cú pháp được giới thiệu trong Java 5 để tránh sự nhàm chán trong việc xác định rõ ràng một trình vòng lặp. Đối với cả hai phong cách, bạn có thể đưa ra sự thay đổi về cơ bản tầm thường sử dụng for, whilehoặc do whilekhối, nhưng tất cả đều đun sôi xuống để điều tương tự (hoặc, đúng hơn, có hai điều).

EDIT: Như @ iX3 chỉ ra trong một bình luận, bạn có thể sử dụng a ListIteratorđể đặt thành phần hiện tại của danh sách khi bạn đang lặp lại. Bạn sẽ cần phải sử dụng List#listIterator()thay vì List#iterator()khởi tạo biến vòng lặp (rõ ràng là sẽ phải được khai báo ListIteratorthay vì một Iterator).


OK, cảm ơn, nhưng nếu iterator thực sự trả về một tham chiếu đến phần tử thực (không phải là bản sao) thì làm cách nào tôi có thể sử dụng nó để thay đổi giá trị trong danh sách? Tôi nghĩ rằng nếu tôi sử dụng e = iterator.next()thì e = somethingElsechỉ cần thay đổi đối tượng eđang đề cập đến thay vì thay đổi cửa hàng thực tế từ đó iterator.next()lấy ra giá trị.
iX3

@ iX3 - Điều đó cũng đúng với phép lặp dựa trên chỉ mục; chỉ định một đối tượng mới esẽ không thay đổi những gì trong danh sách; bạn phải gọi list.set(index, thing). Bạn có thể thay đổi nội dung của e(ví dụ e.setSomething(newValue):), nhưng để thay đổi phần tử nào được lưu trong danh sách khi bạn lặp lại nó, bạn cần phải sử dụng phép lặp dựa trên chỉ mục.
Ted Hopp

Cảm ơn bạn đã giải thích; Tôi nghĩ rằng tôi hiểu những gì bạn đang nói bây giờ và sẽ cập nhật câu hỏi / nhận xét của tôi cho phù hợp. Không có "sao chép" mỗi lần, nhưng do thiết kế ngôn ngữ để thay đổi nội dung của etôi phải gọi một trong ecác phương thức vì gán chỉ thay đổi con trỏ (bỏ qua C của tôi).
iX3

Vì vậy, đối với danh sách các loại bất biến như IntegerStringkhông thể thay đổi nội dung bằng cách sử dụng for-eachhoặc Iteratorphương thức - sẽ phải tự thao tác đối tượng danh sách để thay thế thành phần tử. Có đúng không?
iX3

@ iX3 - Đúng. Bạn có thể xóa các phần tử bằng biểu mẫu thứ ba (trình lặp rõ ràng), nhưng bạn chỉ có thể thêm các phần tử vào hoặc thay thế các phần tử trong danh sách nếu bạn sử dụng phép lặp dựa trên chỉ mục.
Ted Hopp

46

Ví dụ về mỗi loại được liệt kê trong câu hỏi:

ListIterationExample.java

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());
     }
}

23

Vòng lặp cơ bản không được khuyến nghị vì bạn không biết việc thực hiện danh sách.

Nếu đó là LinkedList, mỗi cuộc gọi đến

list.get(i)

sẽ lặp đi lặp lại trong danh sách, dẫn đến độ phức tạp N ^ 2 lần.


1
Chính xác; đó là một điểm tốt, và tôi sẽ cập nhật ví dụ trong câu hỏi.
iX3

Theo bài đăng này, tuyên bố của bạn là không chính xác: Cái nào hiệu quả hơn, một vòng lặp cho mỗi vòng lặp hoặc một vòng lặp?
Hibbem

5
Những gì tôi đọc trong bài viết đó chính xác là những gì tôi đã nói ... Bạn đang đọc chính xác phần nào?
amudeillan

20

Lặp lại theo kiểu JDK8:

public class IterationDemo {

    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3);
        list.stream().forEach(elem -> System.out.println("element " + elem));
    }
}

Cảm ơn, tôi dự định sẽ cập nhật danh sách ở đầu với các giải pháp Java 8.
iX3

1
@ eugene82 cách tiếp cận này hiệu quả hơn nhiều?
nazar_art

1
@nazar_art Bạn sẽ không thấy cải thiện nhiều hơn bất cứ điều gì khác trừ khi bạn có thể đã sử dụngallelStream trên một bộ sưu tập rất lớn. Nó thực sự hiệu quả hơn trong ý nghĩa chỉ chữa lành tính dài dòng.
Rogue

7

Trong Java 8, chúng ta có nhiều cách để lặp lại các lớp bộ sưu tập.

Sử dụng Iterable forEach

Các bộ sưu tập thực hiện Iterable(ví dụ như tất cả các danh sách) hiện có forEachphương thức. Chúng ta có thể sử dụng tham chiếu phương thức được giới thiệu trong Java 8.

Arrays.asList(1,2,3,4).forEach(System.out::println);

Sử dụng Luồng forEach và forEachOrdered

Chúng tôi cũng có thể lặp lại một danh sách bằng Stream dưới dạng:

Arrays.asList(1,2,3,4).stream().forEach(System.out::println);
Arrays.asList(1,2,3,4).stream().forEachOrdered(System.out::println);

Chúng ta nên ưu tiên forEachOrderedhơn forEachvì hành vi của forEachnó không rõ ràng trong khi forEachOrderedthực hiện một hành động cho từng phần tử của luồng này, theo thứ tự bắt gặp của luồng nếu luồng có thứ tự bắt gặp được xác định. Vì vậy, forEach không đảm bảo rằng đơn hàng sẽ được giữ.

Ưu điểm với các luồng là chúng ta cũng có thể sử dụng các luồng song song bất cứ khi nào thích hợp. Nếu mục tiêu chỉ là in các mục không phân biệt thứ tự thì chúng ta có thể sử dụng luồng song song như:

Arrays.asList(1,2,3,4).parallelStream().forEach(System.out::println);

5

Tôi không biết những gì bạn cho là bệnh lý, nhưng hãy để tôi cung cấp một số lựa chọn thay thế mà bạn chưa từng thấy trước đây:

List<E> sl= list ;
while( ! sl.empty() ) {
    E element= sl.get(0) ;
    .....
    sl= sl.subList(1,sl.size());
}

Hoặc phiên bản đệ quy của nó:

void visit(List<E> list) {
    if( list.isEmpty() ) return;
    E element= list.get(0) ;
    ....
    visit(list.subList(1,list.size()));
}

Ngoài ra, một phiên bản đệ quy của cổ điển 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);
}

Tôi đề cập đến chúng bởi vì bạn "hơi mới đối với Java" và điều này có thể thú vị.


1
Cảm ơn đã đề cập đến nó. Tôi chưa bao giờ nhìn vào phương thức subList. Vâng, tôi sẽ coi nó là bệnh hoạn vì tôi không biết bất kỳ tình huống nào sẽ thuận lợi khi sử dụng điều này ngoài các cuộc thi có lẽ gây khó chịu.
iX3

3
ĐỒNG Ý. Tôi không thích bị gọi là "bệnh lý" nên ở đây có một lời giải thích: Tôi đã sử dụng chúng trong khi theo dõi hoặc đi theo những con đường trên cây. Một lần nữa? Trong khi dịch một số chương trình Lisp sang Java, tôi không muốn họ mất tinh thần Lisp và đã làm như vậy. Upvote bình luận nếu bạn nghĩ rằng đây là những sử dụng hợp lệ, không bệnh lý. Tôi cần một cái ôm nhóm !!! :-)
Mario Rossi

Không có đường dẫn trong cây khác với danh sách? BTW, tôi không có ý gọi bạn hoặc bất kỳ người nào là "bệnh hoạn" Tôi chỉ có nghĩa là một số câu trả lời cho câu hỏi của tôi sẽ không thực tế hoặc rất khó có giá trị trong thực hành kỹ thuật phần mềm tốt.
iX3

@ iX3 Đừng lo lắng; Tôi chỉ nói đùa. Đường dẫn là các danh sách: "bắt đầu từ gốc" (rõ ràng), "chuyển sang con thứ 2", "chuyển sang con thứ 1", "chuyển sang con thứ 4". "Dừng lại". Hoặc [2,1,4] cho ngắn gọn. Đây là một danh sách.
Mario Rossi

Tôi muốn tạo một bản sao của danh sách và sử dụng while(!copyList.isEmpty()){ E e = copyList.remove(0); ... }. Đó là hiệu quả hơn so với phiên bản đầu tiên;).
AxelH

2

Bạn có thể sử dụng forEach bắt đầu từ Java 8:

 List<String> nameList   = new ArrayList<>(
            Arrays.asList("USA", "USSR", "UK"));

 nameList.forEach((v) -> System.out.println(v));

0

Đối với một tìm kiếm lạc hậu, bạn nên sử dụng như sau:

for (ListIterator<SomeClass> iterator = list.listIterator(list.size()); iterator.hasPrevious();) {
    SomeClass item = iterator.previous();
    ...
    item.remove(); // For instance.
}

Nếu bạn muốn biết một vị trí, hãy sử dụng iterator.preingly Index (). Nó cũng giúp viết một vòng lặp bên trong so sánh hai vị trí trong danh sách (các vòng lặp không bằng nhau).


0

Phải, nhiều lựa chọn thay thế được liệt kê. Cách dễ nhất và sạch nhất sẽ chỉ là sử dụng forcâu lệnh nâng cao như dưới đây. Đây Expressionlà một số loại có thể lặp lại.

for ( FormalParameter : Expression ) Statement

Ví dụ: để lặp qua, Id <String> id, chúng ta có thể chỉ cần như vậy,

for (String str : ids) {
    // Do something
}

3
Anh ta đang hỏi liệu có cách nào khác ngoài những cách được mô tả trong câu hỏi không (bao gồm cách này mà bạn đã đề cập)!
ericbn

0

Trong java 8bạn có thể sử dụng List.forEach()phương thức với lambda expressionđể lặp qua một danh sách.

import java.util.ArrayList;
import java.util.List;

public class TestA {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("Apple");
        list.add("Orange");
        list.add("Banana");
        list.forEach(
                (name) -> {
                    System.out.println(name);
                }
        );
    }
}

Mát mẻ. Bạn có thể giải thích điều này khác với eugene82câu trả lờii_am_zerocâu trả lời như thế nào không?
iX3

@ iX3 ahh. Không đọc toàn bộ danh sách câu trả lời. Đã cố gắng để có ích .. Bạn có muốn tôi loại bỏ câu trả lời?
Kết quả tìm kiếm Kết quả web Pi

Không quan trọng với tôi, mặc dù có lẽ bạn có thể cải thiện nó bằng cách so sánh và đối chiếu nó với các kỹ thuật khác. Hoặc nếu bạn nghĩ rằng nó không thể hiện bất kỳ kỹ thuật mới nào nhưng vẫn có thể hữu ích như một tài liệu tham khảo cho người khác, có lẽ bạn có thể thêm một ghi chú giải thích điều đó.
iX3

-2

Bạn luôn có thể chuyển ra các ví dụ đầu tiên và thứ ba với một vòng lặp while và thêm một chút mã. Điều này mang lại cho bạn lợi thế của việc có thể sử dụng do-while:

int i = 0;
do{
 E element = list.get(i);
 i++;
}
while (i < list.size());

Tất nhiên, loại điều này có thể gây ra NullPulumException nếu list.size () trả về 0, vì nó luôn được thực thi ít nhất một lần. Điều này có thể được sửa bằng cách kiểm tra nếu phần tử là null trước khi sử dụng thuộc tính / phương thức tho. Tuy nhiên, việc sử dụng vòng lặp for đơn giản và dễ dàng hơn nhiều


Đúng ... tôi đoán đó là khác nhau về mặt kỹ thuật, nhưng hành vi chỉ khác nhau nếu danh sách trống, phải không?
iX3

@ ix3 có, và trong trường hợp đó, hành vi tồi tệ hơn. Tôi sẽ không gọi đây là một sự thay thế tốt.
CPerkins

Chắc chắn, nhưng công bằng mà nói, câu hỏi này ít hơn về những gì "tốt" và nhiều hơn về những gì "có thể", vì vậy tôi vẫn đánh giá cao câu trả lời này.
iX3
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.