Java'da bir liste üzerinde yineleme yolları


576

Java diline biraz yeni gelmek, bir liste (veya belki de diğer koleksiyonlar) ve her birinin avantajları veya dezavantajları aracılığıyla yineleyebileceği tüm yolları (veya en azından patolojik olmayanları) tanımaya çalışıyorum.

Bir List<E> listnesne göz önüne alındığında , tüm öğeler arasında dolaşmak için aşağıdaki yolları biliyorum:

Döngü için temel (elbette, eşdeğer / döngüler de var)whiledo 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

    // ...
}

Not: @amarseillan'ın işaret ettiği gibi, bu form Lists üzerinde yineleme yapmak için kötü bir seçimdir , çünkü getyöntemin gerçek uygulaması bir Iterator. Örneğin LinkedList, i-th öğesini elde etmek için uygulamalar i'den önceki tüm öğeleri geçmelidir.

Yukarıdaki örnekte, Listuygulamanın gelecekteki yinelemeleri daha verimli hale getirmek için "yerini kaydetmesinin" bir yolu yoktur . Çünkü ArrayListbunun önemi yoktur, çünkü karmaşıklığı / maliyeti getsabit süredir (O ​​(1)), a için ise LinkedListlistenin büyüklüğü (O (n)) ile orantılıdır.

Yerleşik Collectionsuygulamaların hesaplama karmaşıklığı hakkında daha fazla bilgi için bu soruya göz atın .

Döngü için geliştirildi ( bu soruda iyi açıklandı )

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

    // ...
}

yineleyici

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

    // ...
}

Fonksiyonel Java

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

Her biri için , yinelenebilir .

(Java 8'in Stream API'sinden bir harita yöntemi (@ i_am_zero'nun cevabına bakın).)

Java 8 toplama sınıflarında Iterable(örneğin, tüm Lists) uygulanan , yukarıda gösterilen for döngüsü ifadesiforEach yerine kullanılabilecek bir yöntem vardır . (İşte iyi bir karşılaştırma sağlayan başka bir soru .)

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

Varsa başka hangi yollar var?

(BTW, ilgim hiç performansı optimize etme arzusundan kaynaklanmıyor ; sadece bir geliştirici olarak hangi formların kullanılabilir olduğunu bilmek istiyorum.)


1
Koleksiyonları işlemek için çeşitli işlevsel tarz kitaplıklarından herhangi birini de kullanabilirsiniz, ancak bunlar patolojik olmayanlardır.
Dave Newton

ListArayüzle ilgili soru özellikle mi?
Sotirios Delimanolis

@SotiriosDelimanolis, tüm niyet ve amaçlar için, evet <code> Liste </code> 'e özgüdür, ancak <code> Koleksiyon </code> ile çalışmanın başka ilginç yolları varsa, onları tanımak istiyorlar.
iX3

@DaveNewton, fikir için teşekkürler. Hiç böyle bir şey kullanmadım. Lütfen düzenlenmiş soruma bir göz atın ve ne demek istediğinizi anladığımı anlayın.
iX3

Yanıtlar:


265

Üç döngü şekli neredeyse aynıdır. Geliştirilmiş fordöngü:

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

göre, bir Java Dil Şartname , aynı geleneksel sahip iterasyon kesin kullanımı için yürürlükte fordöngü. Üçüncü durumda, liste içeriğini yalnızca geçerli öğeyi kaldırarak ve ardından yalnızca removeyineleyicinin kendi yöntemiyle yaparsanız değiştirebilirsiniz . Dizin tabanlı yineleme ile listeyi herhangi bir şekilde değiştirebilirsiniz. Ancak, geçerli dizinden önce gelen öğelerin eklenmesi veya kaldırılması, döngü öğelerinizi atlamayı veya aynı öğeyi birden çok kez işleme riskini taşır; bu tür değişiklikler yaparken döngü dizinini doğru şekilde ayarlamanız gerekir.

Her durumda, elementgerçek liste öğesine bir referanstır. Yineleme yöntemlerinin hiçbiri listedeki herhangi bir şeyin kopyasını oluşturmaz. Dahili durumunda yapılan değişiklikler elementher zaman listedeki ilgili elemanın dahili durumunda görülür.

Esasen, bir liste üzerinde yineleme yapmanın yalnızca iki yolu vardır: bir dizin kullanarak veya bir yineleyici kullanarak. Geliştirilmiş döngü, bir yineleyiciyi açıkça tanımlamaktan kaçınmak için Java 5'te sunulan sözdizimsel bir kısayoldur. Her iki stil için for, whileveya do whileblokları kullanarak esasen önemsiz varyasyonlar ortaya çıkarabilirsiniz , ancak hepsi aynı şeye (veya daha ziyade iki şeye) kaynar.

DÜZENLEME: @ iX3 bir yorumda belirttiği gibi, ListIteratorbir listenin geçerli öğesini yineleme yaparken ayarlamak için a kullanabilirsiniz . Sen kullanmanız gerekir List#listIterator()yerine List#iterator()(tabii ki, bir ilan edilmesi gerekir döngü değişkeni başlatmak için ListIteratorbir yerine Iterator).


Tamam, teşekkürler, ancak yineleyici gerçek öğeye (bir kopyaya değil) gerçekten bir başvuru döndürüyorsa, listedeki değeri değiştirmek için nasıl kullanabilirim? Ben bir kullanırsanız o almak e = iterator.next()o yapıyor e = somethingElsesadece nesne ne gibi değişiklikler eatıfta ziyade hangi fiili deposunu değişiyor iterator.next()değeri alınır.
iX3

@ iX3 - Bu, dizin tabanlı bir yineleme için de geçerli olacaktır; elistedeki içeriği değiştirmeyecek yeni bir nesne atamak ; aramak zorundasın list.set(index, thing). Değişebilirsin içeriğini arasında e(örneğin e.setSomething(newValue)), ancak bunu yineleme gibi listede saklanır hangi öğe değişikliğine, bir dizin tabanlı yineleme ile sopa gerekir.
Ted Hopp

Bu açıklama için teşekkür ederim; Sanırım şimdi ne söylediğini anlıyorum ve sorum / yorumumu buna göre güncelleyeceğim. Per-se "kopya" yoktur, ancak dil tasarımı nedeniyle içeriğinde değişiklik yapmak için 'yöntemlerden ebirini çağırmak gerekir eçünkü atama sadece işaretçiyi değiştirir (affed C).
iX3

Değiştirilemez tiplerin listeleri gibi Yani Integerve Stringo kullanarak içeriğini değiştirmek mümkün olmazdı for-eachya Iteratorelemanlarına yerine liste nesnenin kendisini işlemek zorunda kalacak - yöntemleri. Bu doğru mu?
iX3

@ iX3 - Doğru. Üçüncü biçime (açık yineleyici) sahip öğeleri kaldırabilirsiniz, ancak listedeki öğeleri yalnızca dizin tabanlı yineleme kullanıyorsanız veya öğeleri değiştirebilirsiniz.
Ted Hopp

46

Soruda listelenen her türden örnek:

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

Listenin uygulanmasını bilmediğiniz için temel döngü önerilmez.

Bu bir LinkedList olsaydı, her çağrı

list.get(i)

liste üzerinde yinelenerek N ^ 2 zaman karmaşıklığına neden olur.


1
Doğru; bu iyi bir nokta ve sorudaki örneği güncelleyeceğim.
iX3


5
Bu yazıda okuduğum tam olarak söylediğim şey ... Tam olarak hangi kısmı okuyorsun?
amarseillan

20

JDK8 tarzı bir yineleme:

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

Teşekkürler, sonunda listeyi Java 8 çözümleriyle güncellemeyi planlıyorum.
iX3

1
@ eugene82 bu yaklaşım çok daha verimli mi?
nazar_art

1
@nazar_art Belki çok büyük bir koleksiyonda parallelStream kullanmadıkça başka hiçbir şey üzerinde fazla bir gelişme görmeyeceksiniz. Gerçekten, ayrıntı düzeyini iyileştirme anlamında daha etkilidir.
Rogue

7

In Java 8 biz toplama sınıfları üzerinde yineleme için birden fazla yol var.

Yinelenebilir forEach kullanma

Uygulayan koleksiyonların Iterable(örneğin tüm listeler) artık forEachyöntemi vardır. Java 8'de tanıtılan yöntem referansını kullanabiliriz .

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

Akışları forEach ve forEachOrdered kullanma

Stream'i şu şekilde kullanarak bir liste üzerinde de yineleme yapabiliriz :

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

Biz tercih etmeliyiz forEachOrderedüzerinde forEachdavranışı nedeniyle forEachaçıkça deterministik olmayan olduğu kadar forEachOrderedgerçekleştirir akışı tanımlanmış bir karşılaşma olduğunu kabul edersek akışının karşılaşma için bu akımın her elemanı için bir eylem. Bu nedenle forEach, siparişin korunacağını garanti etmez.

Akarsuların avantajı, uygun olan yerlerde paralel akarsuları da kullanabilmemizdir. Amaç yalnızca siparişe bakılmaksızın öğeleri yazdırmaksa, paralel akışı aşağıdaki gibi kullanabiliriz:

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

5

Patolojik olarak neyi düşündüğünüzü bilmiyorum, ama daha önce göremediğiniz bazı alternatifler sunmama izin verin:

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

Veya özyinelemeli sürümü:

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

Ayrıca, klasikin özyinelemeli bir versiyonu 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);
}

Onlardan bahsediyorum çünkü "Java için biraz yenisiniz" ve bu ilginç olabilir.


1
Bahsettiğiniz için teşekkürler. SubList yöntemine hiç bakmamıştım. Evet, patolojik olarak düşünürüm, çünkü bunu belki de gizleme yarışmaları dışında kullanmanın avantajlı olacağı herhangi bir durumu bilmiyorum.
iX3

3
TAMAM. "Patolojik" olarak adlandırılmayı sevmiyorum, bu yüzden burada bir açıklama var: Ağaçlardaki yolları izlerken veya takip ederken bunları kullandım. Bir tane daha? Bazı Lisp programlarını Java'ya çevirirken Lisp ruhlarını kaybetmelerini istemedim ve aynı şeyi yaptım. Bunların geçerli, patolojik olmayan kullanımlar olduğunu düşünüyorsanız yorumu kaldırın. Bir grup sarılmaya ihtiyacım var !!! :-)
Mario Rossi

Ağaçlardaki yollar listelerden farklı değil mi? BTW, sizi veya herhangi bir kişiyi "patolojik" olarak adlandırmak istemedim. Sadece sorumun bazı cevaplarının anlaşılır bir şekilde pratik olmadığı veya iyi yazılım mühendisliği uygulamasında değerinin olması pek mümkün olmayacağı anlamına geliyordu.
iX3

@ iX3 Endişelenme; Sadece şaka yapıyordum. Yollar listelerdir: "kökten başla" (açık), "2. çocuğa taşı", "1. çocuğa taşı", "4. çocuğa taşı". "Dur". Veya kısaca [2,1,4]. Bu bir liste.
Mario Rossi

Listenin bir kopyasını oluşturmayı ve kullanmayı tercih ederim while(!copyList.isEmpty()){ E e = copyList.remove(0); ... }. Bu ilk sürümden daha etkilidir;).
AxelH

2

Java 8'den başlayarak forEach'ı kullanabilirsiniz :

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

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

0

Geriye doğru arama için aşağıdakileri kullanmalısınız:

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

Bir konum bilmek istiyorsanız iterator.previousIndex () öğesini kullanın. Ayrıca listedeki iki konumu karşılaştıran bir iç döngü yazmaya yardımcı olur (yineleyiciler eşit değildir).


0

Doğru, birçok alternatif listelenmiştir. En kolay ve en temiz for, aşağıdaki gibi gelişmiş ifadeyi kullanmak olacaktır . Bu Expressionyinelenebilir bir türdür.

for ( FormalParameter : Expression ) Statement

Örneğin, <String> kimliklerini listelemek için,

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

3
Soruda açıklananların dışında başka yolların olup olmadığını soruyor (bunu da dahil ettiniz)!
ericbn

0

İçinde bir liste üzerinden yineleme yapmak için ile yöntemini java 8kullanabilirsiniz . 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);
                }
        );
    }
}

Güzel. Bu kadar farklı olduğunu açıklayabilir misiniz eugene82bireyin cevabı ve i_am_zero'ın cevabı ?
iX3

@ iX3 ahh. Tüm cevap listesini okumadım. Yardımcı olmaya çalışıyordu .. Cevabı kaldırmamı ister misin?
Arama Sonuçları Web sonuçları Pi

Benim için önemli değil, ama belki de diğer tekniklerle karşılaştırarak geliştirebilirsin. Ya da yeni bir teknik göstermediğini, ancak yine de başkaları için bir referans olarak yararlı olabileceğini düşünüyorsanız, belki de bunu açıklayan bir not ekleyebilirsiniz.
iX3

-2

Birinci ve üçüncü örnekleri her zaman while döngüsü ve biraz daha kodla değiştirebilirsiniz. Bu size do-while özelliğini kullanabilme avantajı sağlar:

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

Tabii ki, list.size () 0 döndürürse, bu tür şeyler NullPointerException neden olabilir, çünkü her zaman en az bir kez yürütülür. Bu, öğenin özniteliklerini / yöntemlerini kullanmadan önce öğenin boş olup olmadığını test ederek düzeltilebilir. Yine de, for döngüsünü kullanmak çok daha basit ve daha kolay


Doğru ... Sanırım teknik olarak farklı, ama davranış sadece liste boşsa farklı, değil mi?
iX3

@ ix3 evet ve bu durumda davranış daha kötüdür. Buna iyi bir alternatif demezdim.
CPerkins

Tabii, ama adalet içinde, bu soru "iyi" neyin daha az ve "neyin" mümkün olduğu hakkında daha fazladır, bu yüzden hala bu cevabı takdir ediyorum.
iX3
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.