วิธีในการวนซ้ำรายการใน Java


576

ค่อนข้างใหม่กับภาษาจาวาฉันพยายามทำความคุ้นเคยกับทุกวิธี (หรืออย่างน้อยก็ไม่ใช่คนที่ไม่ใช่พยาธิวิทยา) ที่อาจทำซ้ำผ่านรายการ (หรืออาจจะเป็นชุดอื่น ๆ ) และข้อดีหรือข้อเสียของแต่ละคน

รับList<E> 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 ชี้ให้เห็นรูปแบบนี้เป็นทางเลือกที่ดีกว่าสำหรับวนLists เพราะการปฏิบัติจริงของวิธีการอาจจะไม่เป็นที่มีประสิทธิภาพเมื่อใช้get Iteratorตัวอย่างเช่นLinkedListการใช้งานจะต้องสำรวจองค์ประกอบทั้งหมดก่อนหน้า i เพื่อรับองค์ประกอบที่ i

ในตัวอย่างข้างต้นไม่มีวิธีสำหรับการListใช้งานเพื่อ "บันทึกตำแหน่ง" เพื่อให้การทำซ้ำในอนาคตมีประสิทธิภาพมากขึ้น สำหรับArrayListมันไม่สำคัญเพราะความซับซ้อน / ค่าใช้จ่ายของgetเวลาคงที่ (O (1)) ในขณะที่LinkedListมันเป็นสัดส่วนกับขนาดของรายการ (O (n))

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับความซับซ้อนในการคำนวณของการCollectionsใช้งานในตัวโปรดดูคำถามนี้

ปรับปรุงแล้วสำหรับลูป (อธิบายได้ดีในคำถามนี้ )

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

    // ...
}

iterator

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

    // ...
}

ฟังก์ชั่น Java

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

Iterable.forEach , Stream.forEach , ...

(วิธีการแผนที่จาก API สตรีมของ Java 8 (ดูคำตอบของ @ i_am_zero))

ในคลาสคอลเลกชัน Java 8 ที่นำไปใช้Iterable(ตัวอย่างเช่นLists ทั้งหมด) มีforEachเมธอดซึ่งสามารถใช้แทนคำสั่ง for loop ที่แสดงด้านบน (นี่คือคำถามอื่นที่ให้การเปรียบเทียบที่ดี)

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 ความสนใจของฉันไม่ได้เกิดจากความปรารถนาที่จะเพิ่มประสิทธิภาพการทำงาน ; ฉันแค่อยากรู้ว่าแบบฟอร์มใดที่ฉันมีให้ในฐานะนักพัฒนา)


1
สิ่งเหล่านี้ไม่ใช่สิ่งที่พยาธิวิทยาถึงแม้ว่าคุณอาจใช้ไลบรารีสไตล์การทำงานหลายอย่างเพื่อประมวลผลคอลเลกชันเช่นกัน
Dave Newton

มีคำถามเกี่ยวกับListส่วนต่อประสานเฉพาะหรือไม่
Sotirios Delimanolis

@SotiriosDelimanolis สำหรับทุกเจตนารมณ์และวัตถุประสงค์ใช่เฉพาะกับ <code> List </code> แต่หากมีวิธีที่น่าสนใจอื่น ๆ ในการทำงานพูด <code> Collection </code> ฉันจะเป็น สนใจที่จะรู้จักพวกเขา
iX3

@ DaveNewton ขอบคุณสำหรับความคิด ฉันไม่เคยใช้อะไรแบบนั้น โปรดดูคำถามที่แก้ไขแล้วและแจ้งให้เราทราบหากฉันเข้าใจสิ่งที่คุณหมายถึง
iX3

2
@sdasdadas เสร็จแล้ว: stackoverflow.com/questions/18410035/…
iX3

คำตอบ:


265

ทั้งสามรูปแบบของการวนซ้ำเกือบจะเหมือนกัน forห่วงที่เพิ่มขึ้น:

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

เป็นไปตามข้อมูลจำเพาะภาษา Java , เหมือนในผลกระทบต่อการใช้งานอย่างชัดเจนของ iterator กับแบบดั้งเดิมforห่วง ในกรณีที่สามคุณสามารถแก้ไขเนื้อหารายการโดยลบองค์ประกอบปัจจุบันและจากนั้นเฉพาะเมื่อคุณทำผ่านremoveวิธีการทำซ้ำตัวเอง ด้วยการทำซ้ำตามดัชนีคุณมีอิสระในการแก้ไขรายการในทางใดทางหนึ่ง อย่างไรก็ตามการเพิ่มหรือลบองค์ประกอบที่มาก่อนดัชนีปัจจุบันมีความเสี่ยงในการวนซ้ำองค์ประกอบหรือการประมวลผลองค์ประกอบเดียวกันหลายครั้ง คุณต้องปรับดัชนีลูปอย่างเหมาะสมเมื่อคุณทำการเปลี่ยนแปลงดังกล่าว

ในทุกกรณีelementเป็นการอ้างอิงถึงองค์ประกอบรายการจริง ไม่มีวิธีการทำซ้ำใด ๆ ที่จะทำสำเนาสิ่งใด ๆ ในรายการ การเปลี่ยนแปลงสถานะภายในของelementจะปรากฏในสถานะภายในขององค์ประกอบที่สอดคล้องกันในรายการเสมอ

โดยพื้นฐานแล้วมีสองวิธีในการวนซ้ำรายการ: โดยใช้ดัชนีหรือโดยใช้ตัววนซ้ำ Enhanced for loop เป็นเพียงทางลัด syntactic ที่นำมาใช้ใน Java 5 เพื่อหลีกเลี่ยงความเบื่อหน่ายของการกำหนดตัววนซ้ำอย่างชัดเจน สำหรับรูปแบบทั้งที่คุณสามารถมากับการเปลี่ยนแปลงเล็ก ๆ น้อย ๆ เป็นหลักโดยใช้for, whileหรือdo whileบล็อก แต่พวกเขาทั้งหมดต้มลงไปในสิ่งเดียวกัน (หรือมากกว่าสองสิ่ง)

แก้ไข: เนื่องจาก @ iX3 ชี้ให้เห็นในความคิดเห็นคุณสามารถใช้ListIteratorเพื่อตั้งองค์ประกอบปัจจุบันของรายการในขณะที่คุณกำลังทำซ้ำ คุณจะต้องใช้List#listIterator()แทนList#iterator()การเริ่มต้นตัวแปรลูป (ซึ่งแน่นอนจะต้องมีการประกาศListIteratorมากกว่าIterator)


ตกลงขอบคุณ แต่ถ้าตัววนซ้ำส่งคืนการอ้างอิงไปยังองค์ประกอบจริง (ไม่ใช่สำเนา) ดังนั้นฉันจะใช้มันเพื่อเปลี่ยนค่าในรายการได้อย่างไร ฉันใช้มันถ้าฉันใช้e = iterator.next()แล้วทำe = somethingElseเพียงแค่เปลี่ยนสิ่งที่วัตถุที่eอ้างถึงมากกว่าการเปลี่ยนร้านค้าที่เกิดขึ้นจริงซึ่งiterator.next()ดึงค่า
iX3

@ iX3 - นั่นจะเป็นจริงของการทำซ้ำตามดัชนีเช่นกัน การกำหนดวัตถุใหม่เพื่อeไม่เปลี่ยนแปลงสิ่งที่อยู่ในรายการ list.set(index, thing)คุณมีการโทร คุณสามารถเปลี่ยนเนื้อหาของe(เช่น, e.setSomething(newValue)) แต่หากต้องการเปลี่ยนองค์ประกอบที่เก็บไว้ในรายการเมื่อคุณทำการวนซ้ำคุณจะต้องติดกับการวนซ้ำตามดัชนี
Ted Hopp

ขอบคุณสำหรับคำอธิบายนั้น ฉันคิดว่าฉันเข้าใจสิ่งที่คุณพูดตอนนี้และจะอัปเดตคำถาม / ความคิดเห็นของฉันตาม ไม่มี "การคัดลอก" ต่อครั้ง แต่เนื่องจากการออกแบบภาษาเพื่อทำการเปลี่ยนแปลงเนื้อหาของeฉันจะต้องเรียกวิธีการอย่างใดอย่างหนึ่งeเพราะการมอบหมายเพียงแค่เปลี่ยนพอยน์เตอร์ (ให้อภัย C)
iX3

ดังนั้นสำหรับรายการประเภทที่ไม่เปลี่ยนรูปแบบเช่นIntegerและStringมันเป็นไปไม่ได้ที่จะเปลี่ยนเนื้อหาโดยใช้for-eachหรือIteratorวิธีการ - จะต้องจัดการวัตถุรายการตัวเองเพื่อแทนที่องค์ประกอบ นั่นถูกต้องใช่ไหม?
iX3

@ iX3 - ถูกต้อง คุณสามารถลบองค์ประกอบที่มีรูปแบบที่สาม (ชัดเจน iterator) แต่คุณสามารถเพิ่มองค์ประกอบให้กับหรือแทนที่องค์ประกอบในรายการถ้าคุณใช้การทำซ้ำตามดัชนี
Ted Hopp

46

ตัวอย่างของแต่ละประเภทที่ระบุไว้ในคำถาม:

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

ไม่แนะนำให้ใช้การวนรอบพื้นฐานเนื่องจากคุณไม่ทราบว่าการนำไปใช้งานของรายการ

หากนั่นคือ LinkedList การโทรแต่ละครั้งจะถูก

list.get(i)

จะวนซ้ำในรายการส่งผลให้เกิดความซับซ้อนครั้ง N ^ 2


1
แก้ไข; นั่นเป็นประเด็นที่ดีและฉันจะอัปเดตตัวอย่างในคำถาม
iX3

ตามโพสต์นี้ข้อความของคุณไม่ถูกต้อง: ใดมีประสิทธิภาพมากขึ้นสำหรับแต่ละวงหรือวนซ้ำ?
Hibbem

5
สิ่งที่ฉันอ่านในโพสต์นั้นเป็นสิ่งที่ฉันพูด ... คุณกำลังอ่านส่วนไหนกันแน่?
amarseillan

20

การวนซ้ำแบบ 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));
    }
}

ขอบคุณฉันตั้งใจจะอัปเดตรายการที่ด้านบนด้วยโซลูชั่น Java 8 ในที่สุด
iX3

1
@ eugene82 วิธีนี้มีประสิทธิภาพมากกว่านี้ไหม?
nazar_art

1
@nazar_art คุณจะไม่เห็นการปรับปรุงอะไรมากนอกจากคุณอาจใช้ parallelStream ในคอลเล็กชันขนาดใหญ่มาก มันมีประสิทธิภาพมากขึ้นในแง่ของการรักษาความฟุ่มเฟื่อยเท่านั้น
Rogue

7

ในJava 8เรามีหลายวิธีในการทำซ้ำในคลาสคอลเล็กชัน

การใช้ Iterable forEach

คอลเล็กชันที่ใช้Iterable(ตัวอย่างเช่นทุกรายการ) มีforEachเมธอด เราสามารถใช้วิธีการอ้างอิงที่แนะนำใน Java 8

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

ใช้ Streams forEach และ forEachOrdered

นอกจากนี้เรายังสามารถวนซ้ำรายการโดยใช้Streamเป็น:

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

เราควรจะชอบforEachOrderedมากกว่าforEachเพราะพฤติกรรมของforEachnondeterministic อย่างชัดเจนในฐานะที่เป็นforEachOrderedดำเนินการสำหรับแต่ละองค์ประกอบของกระแสนี้ในลำดับการเผชิญหน้าของกระแสถ้ากระแสมีลำดับการเผชิญหน้าที่กำหนดไว้ ดังนั้นสำหรับแต่ละคนไม่รับประกันว่าคำสั่งจะถูกเก็บไว้

ข้อดีของการสตรีมคือเราสามารถใช้สตรีมขนานในทุกที่ที่เหมาะสม หากวัตถุประสงค์เพียงเพื่อพิมพ์รายการโดยไม่คำนึงถึงลำดับเราสามารถใช้สตรีมแบบขนานเป็น:

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

5

ฉันไม่รู้ว่าคุณคิดว่าพยาธิวิทยาเป็นอย่างไร แต่ขอให้ฉันเสนอทางเลือกบางอย่างที่คุณอาจไม่เคยเห็นมาก่อน:

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" และนี่อาจน่าสนใจ


1
ขอบคุณที่พูดถึงมัน ฉันไม่เคยดูวิธี subList ใช่ฉันจะคิดว่ามันเป็นพยาธิสภาพที่ฉันไม่ทราบถึงสถานการณ์ใด ๆ ที่มันจะเป็นประโยชน์ที่จะใช้มันนอกเหนือจากการแข่งขันที่ทำให้งง
iX3

3
ตกลง. ฉันไม่ชอบถูกเรียกว่า "พยาธิวิทยา" ดังนั้นในที่นี้จะมีคำอธิบายอย่างหนึ่ง: ฉันได้ใช้มันในขณะที่ติดตามหรือติดตามเส้นทางในต้นไม้ อีกหนึ่ง? ในขณะที่กำลังแปลโปรแกรม LISP บางรายการไปยัง Java ฉันไม่ต้องการให้สูญเสีย Lisp วิญญาณของพวกเขา โหวตความคิดเห็นถ้าคุณคิดว่าสิ่งเหล่านี้ใช้ได้ถูกต้องและไม่ใช่ทางพยาธิวิทยา ฉันต้องการกอดกลุ่ม !!! :-)
Mario Rossi

เส้นทางในต้นไม้ต่างจากรายการใช่ไหม BTW ฉันไม่ได้หมายถึงการโทรหาคุณหรือบุคคลใด ๆ "พยาธิวิทยา" ฉันแค่หมายความว่าคำตอบบางคำถามของฉันจะเข้าใจไม่ได้หรือไม่น่าจะมีค่าในการฝึกฝนทางวิศวกรรมซอฟต์แวร์
iX3

@ iX3 ไม่ต้องกังวล ฉันแค่ล้อเล่น เส้นทางคือรายการ: "เริ่มต้นที่รูท" (ชัดเจน), "ย้ายไปยังลูกที่สอง", "ย้ายไปที่ลูกที่หนึ่ง", "ย้ายไปที่ลูกที่สี่" "หยุด". หรือ [2,1,4] สำหรับระยะสั้น นี่คือรายการ
Mario Rossi

while(!copyList.isEmpty()){ E e = copyList.remove(0); ... }ฉันชอบที่จะสร้างสำเนาของรายการและการใช้งานที่ นั่นมีประสิทธิภาพมากกว่ารุ่นแรก;)
AxelH


0

สำหรับการค้นหาย้อนหลังคุณควรใช้สิ่งต่อไปนี้:

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

หากคุณต้องการทราบตำแหน่งให้ใช้ iterator.previousIndex () นอกจากนี้ยังช่วยในการเขียนลูปภายในที่เปรียบเทียบสองตำแหน่งในรายการ (ตัววนซ้ำไม่เท่ากัน)


0

ขวามีตัวเลือกมากมายไว้ในรายการ ที่ง่ายและสะอาดที่สุดคือการใช้forคำสั่งที่ปรับปรุงแล้วดังต่อไปนี้ Expressionเป็นบางชนิดที่เป็น iterable

for ( FormalParameter : Expression ) Statement

ตัวอย่างเช่นในการวนซ้ำรายการรหัส <String> เราสามารถทำได้ง่ายๆ

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

3
เขากำลังถามว่ามีวิธีอื่นนอกเหนือจากที่อธิบายไว้ในคำถาม (ซึ่งรวมถึงวิธีนี้ที่คุณพูดถึง)!
ericbn

0

ในjava 8คุณสามารถใช้List.forEach()วิธีด้วยlambda expressionเพื่อวนซ้ำรายการ

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

เย็น. คุณช่วยอธิบายว่าสิ่งนี้แตกต่างจากeugene82คำตอบและi_am_zeroคำตอบได้อย่างไร
iX3

@ iX3 อ่า ไม่ได้อ่านรายการคำตอบทั้งหมด พยายามเป็นประโยชน์ .. คุณต้องการให้ฉันลบคำตอบหรือไม่?
ผลลัพธ์การค้นหาเว็บผลลัพธ์ Pi

ไม่สำคัญสำหรับฉันแม้ว่าคุณจะสามารถปรับปรุงได้โดยการเปรียบเทียบและเปรียบเทียบกับเทคนิคอื่น หรือถ้าคุณคิดว่ามันไม่ได้แสดงให้เห็นถึงเทคนิคใหม่ ๆ แต่ก็อาจจะมีประโยชน์ในฐานะข้อมูลอ้างอิงสำหรับผู้อื่นบางทีคุณอาจเพิ่มบันทึกอธิบายที่
iX3

-2

คุณสามารถสลับตัวอย่างแรกและครั้งที่สามด้วยการวนรอบสักครู่และรหัสอีกเล็กน้อย สิ่งนี้ให้ประโยชน์จากความสามารถในการใช้สิ่งที่ต้องทำในขณะนี้:

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

แน่นอนว่าสิ่งประเภทนี้อาจทำให้เกิด NullPointerException หาก list.size () ส่งคืนค่า 0 เนื่องจากมันจะถูกเรียกใช้งานอย่างน้อยหนึ่งครั้งเสมอ นี้สามารถแก้ไขได้โดยการทดสอบว่าองค์ประกอบเป็นโมฆะก่อนที่จะใช้คุณลักษณะ / วิธีการของมัน ถึงกระนั้นมันก็ง่ายกว่าและง่ายกว่าสำหรับการใช้ลูป


จริง ... ฉันเดาว่ามันแตกต่างกันทางเทคนิค แต่พฤติกรรมจะแตกต่างกันเฉพาะในกรณีที่รายการว่างเปล่าใช่ไหม
iX3

@ ix3 ใช่และในกรณีนั้นพฤติกรรมแย่ลง ฉันจะไม่เรียกสิ่งนี้ว่าเป็นทางเลือกที่ดี
CPerkins

แน่นอน แต่ในความเป็นธรรมคำถามนี้น้อยเกี่ยวกับสิ่งที่ "ดี" และเพิ่มเติมเกี่ยวกับสิ่งที่ "เป็นไปได้" ดังนั้นฉันยังคงชื่นชมคำตอบนี้
iX3
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.