Konwertuj Iterator na ArrayList

241

Biorąc pod uwagę Iterator<Element>, w jaki sposób możemy przekonwertować Iteratordo ArrayList<Element>(lub List<Element>) w najlepszym i najszybszym sposobem możliwe, tak, że możemy użyć ArrayList„s operacje na takich jak on get(index), add(element)itp

Maksim
źródło

Odpowiedzi:

360

Lepiej użyj biblioteki takiej jak Guava :

import com.google.common.collect.Lists;

Iterator<Element> myIterator = ... //some iterator
List<Element> myList = Lists.newArrayList(myIterator);

Kolejny przykład guawy:

ImmutableList.copyOf(myIterator);

lub Kolekcje Apache Commons :

import org.apache.commons.collections.IteratorUtils;

Iterator<Element> myIterator = ...//some iterator

List<Element> myList = IteratorUtils.toList(myIterator);       
Renaud
źródło
8
Nie rozumiem W jaki sposób ArrayList jest zwracany przez, powiedzmy, Guava lepiej niż zwykły ArrayList? Czy robią to w bardziej wydajny sposób? Nawet jeśli jest bardziej wydajny, czy naprawdę warto dodać do projektu dodatkową zależność (i większą złożoność)?
CorayThan
10
@CorayThan mniej kodu + przetestowane metody. Chociaż zgadzam się z tobą, nie dodałbym dodatkowej zależności tylko po to, aby użyć tej metody. Ale z drugiej strony większość moich (dużych) projektów korzysta z Guava lub Apache Commons ...
Renaud
4
@CorayThan Podziel i podbij mojego przyjaciela. Po co pisać metodę, która jest już udostępniana przez bibliotekę i jest testowana? Używamy dużo Apache Commons i Guava, są po prostu niesamowite i pomagają zaoszczędzić czas i pieniądze.
Stephan
5
Zgadzam się z Renaudem i Stephanem. Ponadto, jeśli używasz narzędzia do kompilacji, najłatwiej jest na świecie włączyć te biblioteki ... TAKŻE ... jeśli naprawdę nie chcesz ich uwzględniać, możesz przejść do źródła kodu Apache / Guava i skopiuj to, co tam zrobili: DALEJ lepiej niż marnować swój czas na nowo opracowując setki pięknie zaprojektowanych i przetestowanych kół, które już tam są.
Mike gryzoń
238

W Javie 8 możesz użyć nowej forEachRemainingmetody dodanej do Iteratorinterfejsu:

List<Element> list = new ArrayList<>();
iterator.forEachRemaining(list::add);
Znaki Stuarta
źródło
2
co to znaczy ::? jakie to ma imię wydaje się bezpośrednim odniesieniem do list.add (); i wydaje się też, że jest coś nowego w java8; i dzięki! :)
Aquarius Power
13
@AquariusPower ::Składnia jest nowa w Javie 8 i odnosi się do „odwołania do metody”, które jest skrótową formą lambda. Zobacz tutaj, aby uzyskać więcej informacji: docs.oracle.com/javase/tutorial/java/javaOO/…
Stuart Marks
skąd iteratorpochodzi? jest to nierozwiązany symbol
javadba,
1
@javadba Pierwotne pytanie dotyczyło sposobu konwersji iteratora, który już masz, w nową ArrayList. Na potrzeby tego przykładu zakłada się, że iterator, który już masz, został wywołany iterator.
Stuart Marks
1
To powinna być zaakceptowana odpowiedź, ponieważ jest najkrótsza i nie zależy od bibliotek stron trzecich
DanielCuadra
64

Możesz skopiować iterator do nowej listy, takiej jak ta:

Iterator<String> iter = list.iterator();
List<String> copy = new ArrayList<String>();
while (iter.hasNext())
    copy.add(iter.next());

Zakłada się, że lista zawiera ciągi znaków. Naprawdę nie ma szybszego sposobu na odtworzenie listy z iteratora, utkniesz w ręcznym przechodzeniu do niej i kopiowaniu każdego elementu na nową listę odpowiedniego typu.

EDYTOWAĆ :

Oto ogólna metoda kopiowania iteratora na nową listę w sposób bezpieczny dla typu:

public static <T> List<T> copyIterator(Iterator<T> iter) {
    List<T> copy = new ArrayList<T>();
    while (iter.hasNext())
        copy.add(iter.next());
    return copy;
}

Użyj tego w ten sposób:

List<String> list = Arrays.asList("1", "2", "3");
Iterator<String> iter = list.iterator();
List<String> copy = copyIterator(iter);
System.out.println(copy);
> [1, 2, 3]
Óscar López
źródło
26

Uwaga: istnieje różnica między Iterablei Iterator.

Jeśli masz Iterable, to w Javie 8 możesz użyć tego rozwiązania:

Iterable<Element> iterable = createIterable();
List<Element> array = StreamSupport
    .stream(iterable.spliterator(), false)
    .collect(Collectors.toList());

Jak wiem Collectors.toList()tworzy ArrayListinstancję.

Moim zdaniem w jednym wierszu wygląda dobrze.
Na przykład, jeśli chcesz wrócić List<Element>z jakiejś metody:

return StreamSupport.stream(iter.spliterator(), false).collect(Collectors.toList());
Dub Nazar
źródło
4
Pytanie dotyczy Iteratora <Element> jako punktu wyjścia, a nie Iterowalnego <Element>.
Jaap
1
zgadzam się z powyższym komentarzem. bardzo mylące, że nazwałeś swój Iterable iterator. Oni są zupełnie inni.
Stephen Harrison
W Javie 8 możesz łatwo przekonwertować Iteratorpowrót do pojedynczego użycia Iterable za pomocą () -> iterator. Może to być przydatne w takich sytuacjach, ale pozwólcie, że jeszcze raz podkreślę ważną rzecz: Możesz użyć tej metody tylko wtedy, gdy jedno użycie Iterable jest dopuszczalne. Dzwonienie iterable.iterator()więcej niż raz da nieoczekiwane rezultaty. Powyższa odpowiedź staje sięIterator<Element> iterator = createIterator(); List<Element> array = StreamSupport.stream(((Iterable<Element>) () -> iterable).spliterator(), false).collect(toList());
Nexus
9

Całkiem zwięzłe rozwiązanie z prostym Java 8 za pomocą java.util.stream:

public static <T> ArrayList<T> toArrayList(final Iterator<T> iterator) {
    return StreamSupport
        .stream(
            Spliterators
                .spliteratorUnknownSize(iterator, Spliterator.ORDERED), false)
        .collect(
                Collectors.toCollection(ArrayList::new)
    );
}
Xehpuk
źródło
Czy istnieje bardziej kompaktowy sposób na napisanie tego przy użyciu interfejsu API Stream? Wydaje się, że nie jest to prostsze niż normalny sposób pętli while.
xi.lin
37
Nie nazwałbym tego rozwiązania „zwięzłym”.
Sergio
1
@Sergio Właśnie dlatego napisałem „ładny”. Jednak nie potrzebuje zmiennych lokalnych i tylko jednego średnika. Możesz go skrócić za pomocą importu statycznego.
xehpuk
1
Powiedziałbym, że iterator.forEachRemaining( list::add )także nowy w Javie 8 jest o wiele bardziej zwięzły. CollectorWepchnięcie zmiennej listy do nie poprawia czytelności w tym przypadku, ponieważ musi być obsługiwana przez a Streami a Spliterator.
Sheepy
1
@Sergio Nie jest krótki, ale to pojedyncze wyrażenie, które robi ogromną różnicę.
michaelsnowden
5
List result = new ArrayList();
while (i.hasNext()){
    result.add(i.next());
}
Akvel
źródło
11
@LuggiMendoza: Przepełnienie stosu nie jest miejscem, w którym można po prostu wyciąć, wkleić i rozwiązać problem. Ma to być informacyjny. To jest doskonale pouczająca odpowiedź i każda rozsądna osoba powinna być w stanie złożyć w całość, że ja byłam iteratorem. Z dźwięków tego prawie nie starasz się zrozumieć, co się dzieje.
CaTalyst.X
2

Spróbuj StickyListz Cactoos :

List<String> list = new StickyList<>(iterable);

Oświadczenie: Jestem jednym z programistów.

yegor256
źródło
To nie odpowiada na pytanie. Iterable! =Iterator
xehpuk
Świetnie, teraz prawdopodobnie musisz wygenerować JavaDoc dla nowej wersji i zaktualizować link. :)
xehpuk
2

Oto linijka korzystająca ze strumieni

Iterator<?> iterator = ...
List<?> list = StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false)
                .collect(Collectors.toList());
apflieger
źródło
1

Chcę tylko wskazać na pozornie oczywiste rozwiązanie, które NIE zadziała:

Lista list = Stream.generate (iterator :: next)
    .collect (Collectors.toList ());

To dlatego, że Stream#generate(Supplier<T>)może tworzyć tylko nieskończone strumienie, nie spodziewa się, że jego argument zostanie rzucony NoSuchElementException(to Iterator#next()zrobi w końcu).

Zamiast tego należy użyć odpowiedzi xehpuk, jeśli Twój wybór to Iterator → Strumień → Lista.

Sasha
źródło
0

użyj google guava !

Iterable<String> fieldsIterable = ...
List<String> fields = Lists.newArrayList(fieldsIterable);

++

fedevo
źródło
Iterator (nieodwracalny) do ArrayList
Chthonic Project
-2

Tutaj w tym przypadku, jeśli chcesz najszybszy możliwy sposób, for loopto lepiej.

Iterator na próbce wielkości 10,000 runsujęć, 40 msgdzie dla ujęć pętli2 ms

        ArrayList<String> alist = new ArrayList<String>();  
        long start, end;  

        for (int i = 0; i < 1000000; i++) {  
            alist.add(String.valueOf(i));  
        }  

        ListIterator<String> it = alist.listIterator();      

        start = System.currentTimeMillis();  
        while (it.hasNext()) {  
            String s = it.next();  
        }  
        end = System.currentTimeMillis();  

        System.out.println("Iterator start: " + start + ", end: " + end + ", delta: "  
            + (end - start));  
        start = System.currentTimeMillis();  
        int ixx = 0;  
        for (int i = 0; i < 100000; i++) {  
            String s = alist.get(i);  
        }  

        System.out.println(ixx);  
        end = System.currentTimeMillis();  
        System.out.println("for loop start: " + start + ", end: " + end + ", delta: "  
            + (end - start));  

Zakłada się, że lista zawiera ciągi znaków.

Vikiiii
źródło
3
Z pewnością używanie forpętli i uzyskiwanie dostępu do elementów listy get(i)jest szybsze niż używanie iteratora ... ale nie o to prosił OP, szczególnie wspomniał, że iterator jest podawany jako dane wejściowe.
Óscar López
@Oscar przepraszam. Czy powinienem usunąć swoją odpowiedź?
vikiiii
To twój telefon. Nie usunęłbym tego jeszcze, może to być pouczające. Usuwam odpowiedzi tylko wtedy, gdy ludzie zaczynają je głosować :)
Óscar López
Myślę, że @ ÓscarLópez ma rację ... to może nie być wymagana odpowiedź, ale zawiera przydatne informacje dla czytelników (takich jak ja: P).
Chthonic Project,
Masz błąd w swojej odpowiedzi. W get(int)pętli patrzysz tylko na 100 000 wpisów o długości 1 m. Jeśli zmienisz tę pętlę it.size(), zobaczysz, że te 2 metody są zbliżone do tej samej prędkości. Ogólnie rzecz biorąc, tego rodzaju testy prędkości java dostarczają ograniczonych informacji na temat rzeczywistych wyników i należy na nie spojrzeć sceptycznie.
Gray