Oba te interfejsy definiują tylko jedną metodę
public operator fun iterator(): Iterator<T>
Dokumentacja mówi, Sequence
że ma być leniwy. Ale czy nie jest Iterable
też leniwy (chyba że jest poparty przez Collection
)?
Oba te interfejsy definiują tylko jedną metodę
public operator fun iterator(): Iterator<T>
Dokumentacja mówi, Sequence
że ma być leniwy. Ale czy nie jest Iterable
też leniwy (chyba że jest poparty przez Collection
)?
Odpowiedzi:
Kluczowa różnica polega na semantyce i implementacji funkcji rozszerzających stdlib dla Iterable<T>
i Sequence<T>
.
W przypadku Sequence<T>
, gdy to możliwe, funkcje rozszerzeń działają leniwie, podobnie jak pośrednie operacje strumieni Java . Na przykład Sequence<T>.map { ... }
zwraca inny Sequence<R>
i faktycznie nie przetwarza elementów, dopóki nie zostanie wywołana operacja terminala , taka jak toList
lub fold
.
Rozważ ten kod:
val seq = sequenceOf(1, 2)
val seqMapped: Sequence<Int> = seq.map { print("$it "); it * it } // intermediate
print("before sum ")
val sum = seqMapped.sum() // terminal
Drukuje:
before sum 1 2
Sequence<T>
jest przeznaczony do leniwego użytkowania i wydajnego przetwarzania potokowego, gdy chcesz maksymalnie zredukować pracę wykonywaną w operacjach terminalowych , tak samo jak w przypadku strumieni Java. Jednak lenistwo wprowadza pewien narzut, co jest niepożądane w przypadku zwykłych prostych przekształceń mniejszych kolekcji i sprawia, że są mniej wydajne.
Ogólnie rzecz biorąc, nie ma dobrego sposobu na określenie, kiedy jest to potrzebne, więc w Kotlin stdlib lenistwo jest jawne i wyodrębniane do Sequence<T>
interfejsu, aby uniknąć używania go Iterable
domyślnie na wszystkich s.
Na Iterable<T>
, na Przeciwnie, funkcje Przedłużacz z pośrednich semantyki operacyjnych pracują gorliwie, przetwarzać elementy od razu i powrócić inny Iterable
. Na przykład Iterable<T>.map { ... }
zwraca a List<R>
z wynikami mapowania.
Odpowiedni kod dla Iterable:
val lst = listOf(1, 2)
val lstMapped: List<Int> = lst.map { print("$it "); it * it }
print("before sum ")
val sum = lstMapped.sum()
To drukuje:
1 2 before sum
Jak wspomniano powyżej, Iterable<T>
domyślnie nie jest leniwy, a to rozwiązanie pokazuje się dobrze: w większości przypadków ma dobrą lokalizację odniesienia, dzięki czemu wykorzystuje pamięć podręczną procesora, przewidywanie, wstępne pobieranie itp., Więc nawet wielokrotne kopiowanie kolekcji nadal działa dobrze wystarczająco dużo i działa lepiej w prostych przypadkach z małymi zbiorami.
Jeśli potrzebujesz większej kontroli nad potokiem oceny, istnieje jawna konwersja do leniwej sekwencji z Iterable<T>.asSequence()
funkcją function.
map
, filter
a inni nie posiadają wystarczających informacji, aby zdecydować, inne niż z rodzaju Źródło, a ponieważ większość kolekcje są również iterable, że nie jest dobrym markerem „leń”, ponieważ jest powszechnie WSZĘDZIE. leniwy musi być wyraźny, aby był bezpieczny.
Uzupełnianie odpowiedzi klawisza skrótu:
Ważne jest, aby zauważyć, jak sekwencja i iteracja iterują w twoich elementach:
Przykład sekwencji:
list.asSequence().filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
Wynik dziennika:
filtr - Mapa - Każdy; filtr - Mapa - Każdy
Przykład iterowalny:
list.filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
filtr - filtr - Mapa - Mapa - Każda - Każda
Iterable
jest mapowany najava.lang.Iterable
interfejs wJVM
i jest implementowany przez powszechnie używane kolekcje, takie jak Lista lub Zestaw. Funkcje rozszerzające kolekcję na nich są oceniane chętnie, co oznacza, że wszystkie natychmiast przetwarzają wszystkie elementy w swoich danych wejściowych i zwracają nową kolekcję zawierającą wynik.Oto prosty przykład użycia funkcji kolekcji w celu uzyskania nazwisk pierwszych pięciu osób z listy w wieku co najmniej 21 lat:
val people: List<Person> = getPeople() val allowedEntrance = people .filter { it.age >= 21 } .map { it.name } .take(5)
Platforma docelowa: JVM Running on kotlin v. 1.3.61 Najpierw sprawdzanie wieku jest wykonywane dla każdej osoby na liście, a wynik umieszczany jest na zupełnie nowej liście. Następnie mapowanie na ich imiona jest wykonywane dla każdej osoby, która pozostała po operatorze filtru, kończąc na kolejnej nowej liście (to jest teraz a
List<String>
). Na koniec utworzono ostatnią nową listę zawierającą pięć pierwszych elementów poprzedniej listy.Natomiast Sequence to nowa koncepcja Kotlina, która przedstawia leniwie oceniany zbiór wartości. Te same rozszerzenia kolekcji są dostępne dla
Sequence
interfejsu, ale natychmiast zwracają wystąpienia Sequence, które reprezentują przetworzony stan daty, ale bez faktycznego przetwarzania żadnych elementów. Aby rozpocząć przetwarzanie,Sequence
należy zakończyć je z operatorem terminala, są to w zasadzie żądanie do Sekwencji o zmaterializowanie danych, które reprezentuje, w jakiejś konkretnej formie. Przykłady obejmujątoList
,toSet
isum
, żeby wymienić tylko kilka. Gdy zostaną one wywołane, tylko minimalna wymagana liczba elementów zostanie przetworzona w celu uzyskania żądanego wyniku.Przekształcenie istniejącej kolekcji w sekwencję jest całkiem proste, wystarczy użyć
asSequence
rozszerzenia. Jak wspomniano powyżej, musisz również dodać operatora terminala, w przeciwnym razie Sekwencja nigdy nie będzie przetwarzać (znowu, leniwy!).
val people: List<Person> = getPeople() val allowedEntrance = people.asSequence() .filter { it.age >= 21 } .map { it.name } .take(5) .toList()
Platforma docelowa: JVMRunning na kotlin v. 1.3.61 W tym przypadku instancje Person w Sekwencji są sprawdzane pod kątem ich wieku, jeśli zdają, mają wyodrębnione nazwisko, a następnie dodane do listy wyników. Powtarza się to dla każdej osoby z oryginalnej listy, aż zostanie znalezionych pięć osób. W tym momencie funkcja toList zwraca listę, a reszta osób w elemencie
Sequence
nie jest przetwarzana.Jest też coś więcej, do czego zdolna jest Sekwencja: może zawierać nieskończoną liczbę elementów. Patrząc z tego punktu widzenia, sensowne jest, aby operatorzy pracowali tak, jak robią - operator na nieskończonej sekwencji nigdy nie mógłby wrócić, gdyby wykonał swoją pracę chętnie.
Jako przykład, oto sekwencja, która wygeneruje tyle potęg 2, ile wymaga operator terminalu (ignorując fakt, że szybko się to przepełni):
generateSequence(1) { n -> n * 2 } .take(20) .forEach(::println)
Więcej znajdziesz tutaj .
Java
(głównieGuava
) fanów