Cara untuk mengulang daftar di Jawa

576

Menjadi agak baru dalam bahasa Jawa saya mencoba membiasakan diri dengan semua cara (atau setidaknya yang non-patologis) yang orang mungkin beralih melalui daftar (atau mungkin koleksi lain) dan kelebihan atau kekurangan masing-masing.

Diberikan List<E> listobjek, saya tahu cara-cara berikut untuk mengulang semua elemen:

Dasar untuk loop (tentu saja, ada yang setara while/ do whileloop juga)

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

    // ...
}

Catatan: Seperti yang ditunjukkan @amarseillan, formulir ini adalah pilihan yang buruk untuk iterasi List, karena implementasi sebenarnya dari getmetode ini mungkin tidak seefisien ketika menggunakan Iterator. Sebagai contoh, LinkedListimplementasi harus melintasi semua elemen sebelum i untuk mendapatkan elemen ke-i.

Dalam contoh di atas tidak ada cara bagi Listimplementasi untuk "menyelamatkan tempatnya" untuk membuat iterasi masa depan lebih efisien. Untuk suatu ArrayListitu tidak terlalu penting, karena kompleksitas / biaya getadalah waktu yang konstan (O (1)) sedangkan untuk a LinkedListapakah sebanding dengan ukuran daftar (O (n)).

Untuk informasi lebih lanjut tentang kompleksitas komputasi dari Collectionsimplementasi bawaan, lihat pertanyaan ini .

Ditingkatkan untuk loop (dijelaskan dengan baik dalam pertanyaan ini )

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 fungsional

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

Iterable.forEach , Stream.forEach , ...

(Metode peta dari Stream API Java 8 (lihat jawaban @ i_am_zero).)

Di Java 8 kumpulan kelas yang mengimplementasikan Iterable(misalnya, semua Lists) sekarang memiliki forEachmetode, yang dapat digunakan sebagai ganti untuk pernyataan loop ditunjukkan di atas. (Ini adalah pertanyaan lain yang memberikan perbandingan yang bagus.)

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

Apa ada cara lain, jika ada?

(BTW, minat saya sama sekali bukan berasal dari keinginan untuk mengoptimalkan kinerja ; Saya hanya ingin tahu bentuk apa yang tersedia bagi saya sebagai pengembang.)

iX3
sumber
1
Itu adalah yang non-patologis, meskipun Anda mungkin juga menggunakan salah satu dari beberapa pustaka gaya fungsional untuk memproses koleksi juga.
Dave Newton
Apakah pertanyaan tentang Listantarmuka khusus?
Sotirios Delimanolis
@SotiriosDelimanolis, untuk semua maksud dan tujuan, ya itu khusus untuk <code> Daftar </code>, tetapi jika ada cara menarik lainnya untuk bekerja dengan, katakanlah, <code> Collection </code>, saya akan tertarik untuk mengenal mereka.
iX3
@ DaveNewton, terima kasih atas idenya. Saya tidak pernah menggunakan hal seperti itu. Silakan lihat pertanyaan saya yang diedit dan beri tahu saya jika saya mengerti apa yang Anda maksud.
iX3
2
@sdasdadas, selesai: stackoverflow.com/questions/18410035/…
iX3

Jawaban:

265

Tiga bentuk perulangan hampir identik. forLingkaran yang disempurnakan :

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

adalah, menurut Spesifikasi Bahasa Jawa , identik dalam efek dengan penggunaan eksplisit dari iterator dengan forloop tradisional . Dalam kasus ketiga, Anda hanya dapat mengubah daftar isi dengan menghapus elemen saat ini dan, kemudian, hanya jika Anda melakukannya melalui removemetode iterator itu sendiri. Dengan iterasi berbasis indeks, Anda bebas untuk mengubah daftar dengan cara apa pun. Namun, menambahkan atau menghapus elemen yang ada sebelum indeks saat ini berisiko memiliki elemen Anda melewatkan elemen atau memproses elemen yang sama beberapa kali; Anda perlu menyesuaikan indeks loop dengan benar saat Anda melakukan perubahan tersebut.

Dalam semua kasus, elementadalah referensi ke elemen daftar aktual. Tak satu pun dari metode iterasi membuat salinan apa pun dalam daftar. Perubahan pada keadaan internal elementakan selalu terlihat dalam keadaan internal elemen yang sesuai pada daftar.

Pada dasarnya, hanya ada dua cara untuk mengulangi daftar: dengan menggunakan indeks atau dengan menggunakan iterator. Enhanced for loop hanyalah pintasan sintaksis yang diperkenalkan di Java 5 untuk menghindari kebosanan mendefinisikan iterator secara eksplisit. Untuk kedua gaya, Anda dapat membuat variasi yang pada dasarnya sepele menggunakan for, whileatau do whileblok, tetapi semuanya bermuara pada hal yang sama (atau, lebih tepatnya, dua hal).

EDIT: Seperti @ iX3 tunjukkan dalam komentar, Anda dapat menggunakan a ListIteratoruntuk mengatur elemen daftar saat ini saat Anda mengulangi. Anda perlu menggunakan List#listIterator()alih-alih List#iterator()menginisialisasi variabel loop (yang, jelas, harus dinyatakan sebagai ListIteratorbukan Iterator).

Ted Hopp
sumber
OK, terima kasih, tetapi jika iterator benar-benar mengembalikan referensi ke elemen nyata (bukan salinan) lalu bagaimana saya bisa menggunakannya untuk mengubah nilai dalam daftar? Saya menganggap bahwa jika saya menggunakan e = iterator.next()lalu melakukan e = somethingElsehanya mengubah objek apa eyang merujuk daripada mengubah toko yang sebenarnya dari mana iterator.next()mengambil nilai.
iX3
@ iX3 - Itu juga berlaku untuk iterasi berbasis indeks; menugaskan objek baru untuk etidak akan mengubah apa yang ada di daftar; kamu harus menelepon list.set(index, thing). Anda dapat mengubah isi dari e(misalnya, e.setSomething(newValue)), tetapi untuk perubahan apa elemen disimpan dalam daftar seperti Anda iterasi itu, Anda harus tetap dengan iterasi berbasis indeks.
Ted Hopp
Terima kasih atas penjelasannya; Saya rasa saya mengerti apa yang Anda katakan sekarang dan akan memperbarui pertanyaan / komentar saya sesuai. Tidak ada "penyalinan" per-se, tetapi karena desain bahasa untuk membuat perubahan pada isi esaya harus memanggil salah satu emetode karena tugas hanya mengubah pointer (maafkan C saya).
iX3
Jadi untuk daftar jenis yang tidak dapat diubah seperti Integerdan Stringtidak mungkin untuk mengubah konten menggunakan for-eachatau Iteratormetode - harus memanipulasi objek daftar itu sendiri untuk diganti ke elemen. Apakah itu benar?
iX3
@ iX3 - Benar. Anda bisa menghapus elemen dengan bentuk ketiga (iterator eksplisit), tetapi Anda hanya bisa menambahkan elemen ke atau mengganti elemen dalam daftar jika Anda menggunakan iterasi berbasis indeks.
Ted Hopp
46

Contoh setiap jenis yang tercantum dalam pertanyaan:

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

Pengulangan dasar tidak disarankan karena Anda tidak tahu implementasi daftar.

Jika itu adalah LinkedList, setiap panggilan ke

list.get(i)

akan mengulangi daftar, menghasilkan kompleksitas waktu N ^ 2.

amarseillan
sumber
1
Benar; itu adalah poin yang bagus, dan saya akan memperbarui contoh dalam pertanyaan.
iX3
menurut posting ini, pernyataan Anda tidak benar: Mana yang lebih efisien, untuk setiap loop, atau iterator?
Hibbem
5
Apa yang saya baca di pos itu persis seperti yang saya katakan ... Bagian mana yang Anda baca sebenarnya?
amarseillan
20

Iterasi gaya 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));
    }
}
eugene82
sumber
Terima kasih, saya bermaksud memperbarui daftar di atas dengan solusi Java 8 pada akhirnya.
iX3
1
@ eugene82 apakah pendekatan ini jauh lebih efisien?
nazar_art
1
@nazar_art Anda tidak akan melihat banyak peningkatan dibandingkan hal lain kecuali Anda mungkin menggunakan parallelStream pada koleksi yang sangat besar. Ini lebih efisien dalam arti hanya menyembuhkan verbosity, sungguh.
Rogue
7

Di Java 8 kami memiliki banyak cara untuk beralih dari kelas koleksi.

Menggunakan Iterable forEach

Koleksi yang menerapkan Iterable(misalnya semua daftar) sekarang memiliki forEachmetode. Kita dapat menggunakan metode-referensi yang diperkenalkan di Java 8.

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

Menggunakan Streams untuk setiap orang dan untuk setiap orang disiapkan

Kami juga dapat mengulangi daftar menggunakan Stream sebagai:

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

Kita harus memilih forEachOrderedlebih dari forEachkarena perilaku forEachsecara eksplisit nondeterministic di mana sebagai forEachOrderedmelakukan tindakan untuk setiap elemen aliran ini, dalam urutan pertemuan aliran jika aliran memiliki urutan pertemuan yang ditentukan. Jadi forEach tidak menjamin bahwa pesanan akan disimpan.

Keuntungan dengan stream adalah kita juga dapat menggunakan stream paralel dimanapun sesuai. Jika tujuannya hanya untuk mencetak barang-barang terlepas dari pesanan maka kita dapat menggunakan aliran paralel sebagai:

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

Saya tidak tahu apa yang Anda anggap patologis, tetapi izinkan saya memberikan beberapa alternatif yang belum pernah Anda lihat sebelumnya:

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

Atau versi rekursifnya:

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

Juga, versi rekursif klasik 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);
}

Saya menyebutkannya karena Anda "agak baru ke Jawa" dan ini bisa menarik.

Mario Rossi
sumber
1
Terima kasih telah menyebutkannya. Saya belum pernah melihat metode subList. Ya, saya akan menganggapnya patologis karena saya tidak tahu keadaan apa pun di mana akan menguntungkan untuk menggunakannya selain dari kontes yang mungkin membingungkan.
iX3
3
BAIK. Saya tidak suka disebut "patologis" jadi begini satu penjelasan: Saya telah menggunakannya saat melacak atau mengikuti jalan di pohon. Satu lagi? Saat menerjemahkan beberapa program Lisp ke Jawa saya tidak ingin mereka kehilangan semangat Lisp mereka, dan melakukan hal yang sama. Jawab komentar jika menurut Anda ini valid, penggunaan non-patologis. Saya butuh pelukan grup !!! :-)
Mario Rossi
Bukankah jalan di pohon berbeda dari daftar? BTW, saya tidak bermaksud menyebut Anda atau orang lain sebagai "patologis". Saya hanya bermaksud bahwa beberapa jawaban atas pertanyaan saya dapat dimengerti tidak praktis atau sangat tidak mungkin memiliki nilai dalam praktik rekayasa perangkat lunak yang baik.
iX3
@ iX3 Jangan khawatir; Saya hanya bercanda. Path adalah daftar: "mulai dari root" (jelas), "pindah ke anak ke-2", "pindah ke anak ke-1", "pindah ke anak ke-4". "Berhenti". Atau [2,1,4] singkatnya. Ini daftar.
Mario Rossi
Saya lebih suka membuat salinan daftar dan menggunakan while(!copyList.isEmpty()){ E e = copyList.remove(0); ... }. Itu lebih efektif daripada versi pertama;).
AxelH
2

Anda dapat menggunakan forEach mulai dari Java 8:

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

 nameList.forEach((v) -> System.out.println(v));
Sudip Bhandari
sumber
0

Untuk pencarian mundur Anda harus menggunakan yang berikut:

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

Jika Anda ingin mengetahui posisi, gunakan iterator.previousIndex (). Ini juga membantu untuk menulis loop batin yang membandingkan dua posisi dalam daftar (iterator tidak sama).

CoolMind
sumber
0

Benar, banyak alternatif terdaftar. Yang termudah dan terbersih hanya akan menggunakan forpernyataan yang disempurnakan seperti di bawah ini. Ini Expressiondari beberapa jenis yang dapat diubah.

for ( FormalParameter : Expression ) Statement

Misalnya, untuk beralih melalui, Daftar <String> id, kita bisa begitu,

for (String str : ids) {
    // Do something
}
Yu Chen
sumber
3
Dia bertanya apakah ada cara lain selain yang dijelaskan pada pertanyaan (yang termasuk yang Anda sebutkan ini)!
ericbn
0

Di java 8Anda dapat menggunakan List.forEach()metode dengan lambda expressionuntuk mengulangi daftar.

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);
                }
        );
    }
}
Hasil Pencarian Hasil Web Pi
sumber
Keren. Bisakah Anda menjelaskan bagaimana ini berbeda dari eugene82jawaban dan i_am_zerojawaban ?
iX3
@ iX3 ahh. Tidak membaca seluruh daftar jawaban. Sedang berusaha membantu ... Apakah Anda ingin saya menghapus jawabannya?
Hasil Pencarian Hasil web Pi
Tidak masalah bagi saya, meskipun mungkin Anda bisa memperbaikinya dengan membandingkan dan membandingkannya dengan teknik lain. Atau jika Anda berpikir itu tidak menunjukkan teknik baru tetapi mungkin masih berguna sebagai referensi untuk orang lain, mungkin Anda bisa menambahkan catatan yang menjelaskan hal itu.
iX3
-2

Anda selalu dapat mengganti contoh pertama dan ketiga dengan loop sementara dan sedikit kode lagi. Ini memberi Anda keuntungan karena dapat menggunakan do-while:

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

Tentu saja, hal semacam ini dapat menyebabkan NullPointerException jika list.size () mengembalikan 0, karena selalu dieksekusi setidaknya sekali. Ini dapat diperbaiki dengan menguji apakah elemennya nol sebelum menggunakan atribut / metode tho. Tetap saja, ini jauh lebih sederhana dan lebih mudah digunakan untuk for loop

shieldgenerator7
sumber
Benar ... Saya kira itu berbeda secara teknis, tetapi perilakunya hanya berbeda jika daftarnya kosong, bukan?
iX3
@ ix3 ya, dan dalam hal ini, perilakunya lebih buruk. Saya tidak akan menyebut ini alternatif yang baik.
CPerkins
Tentu, tetapi dalam keadilan, pertanyaan ini kurang tentang apa yang "baik" dan lebih banyak tentang apa yang "mungkin", jadi saya masih menghargai jawaban ini.
iX3