Beide Schnittstellen definieren nur eine Methode
public operator fun iterator(): Iterator<T>
Die Dokumentation sagt Sequence
, es soll faul sein. Aber ist das nicht auch Iterable
faul (es sei denn, es wird von a unterstützt Collection
)?
Beide Schnittstellen definieren nur eine Methode
public operator fun iterator(): Iterator<T>
Die Dokumentation sagt Sequence
, es soll faul sein. Aber ist das nicht auch Iterable
faul (es sei denn, es wird von a unterstützt Collection
)?
Antworten:
Der Hauptunterschied liegt in der Semantik und der Implementierung der stdlib-Erweiterungsfunktionen für Iterable<T>
und Sequence<T>
.
Denn Sequence<T>
die Erweiterungsfunktionen arbeiten nach Möglichkeit träge, ähnlich wie Java Streams- Zwischenoperationen . Gibt beispielsweise eine Sequence<T>.map { ... }
andere zurück Sequence<R>
und verarbeitet die Elemente erst dann, wenn eine Terminaloperation wie toList
oder fold
aufgerufen wird.
Betrachten Sie diesen Code:
val seq = sequenceOf(1, 2)
val seqMapped: Sequence<Int> = seq.map { print("$it "); it * it } // intermediate
print("before sum ")
val sum = seqMapped.sum() // terminal
Es druckt:
before sum 1 2
Sequence<T>
ist für eine verzögerte Verwendung und ein effizientes Pipelining gedacht, wenn Sie die Arbeit im Terminalbetrieb so weit wie möglich reduzieren möchten, genau wie bei Java Streams. Faulheit führt jedoch zu einem gewissen Overhead, der für übliche einfache Transformationen kleinerer Sammlungen unerwünscht ist und sie weniger leistungsfähig macht.
Im Allgemeinen gibt es keine gute Möglichkeit, um festzustellen, wann es benötigt wird. Daher wird in Kotlin stdlib faul gemacht und in die Sequence<T>
Schnittstelle extrahiert, um zu vermeiden, dass es Iterable
standardmäßig auf allen s verwendet wird.
Im Iterable<T>
Gegensatz dazu arbeiten die Erweiterungsfunktionen mit Zwischenoperationssemantik eifrig, verarbeiten die Elemente sofort und geben eine andere zurück Iterable
. Gibt beispielsweise Iterable<T>.map { ... }
a List<R>
mit den darin enthaltenen Mapping-Ergebnissen zurück.
Der entsprechende Code für Iterable:
val lst = listOf(1, 2)
val lstMapped: List<Int> = lst.map { print("$it "); it * it }
print("before sum ")
val sum = lstMapped.sum()
Dies druckt aus:
1 2 before sum
Wie oben erwähnt, Iterable<T>
ist es standardmäßig nicht faul, und diese Lösung zeigt sich gut: In den meisten Fällen weist sie eine gute Referenzlokalität auf, wodurch der CPU-Cache, die Vorhersage, das Vorabrufen usw. genutzt werden, sodass auch das mehrfache Kopieren einer Sammlung noch gut funktioniert genug und ist in einfachen Fällen mit kleinen Sammlungen besser.
Wenn Sie mehr Kontrolle über die Auswertungspipeline benötigen, erfolgt eine explizite Konvertierung in eine verzögerte Sequenz mit Iterable<T>.asSequence()
Funktion.
map
, filter
und andere tragen nicht genügend Informationen , andere zu entscheiden , als von der Quelle Kollektionstyp, und da die meisten Sammlungen auch Iterable sind, das ist kein guter Marker für „faul“ , weil es häufig überall. faul muss explizit sein, um sicher zu sein.
Antwort des Hotkeys vervollständigen:
Es ist wichtig zu beachten, wie Sequence und Iterable in Ihren Elementen iteriert:
Sequenzbeispiel:
list.asSequence().filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
Protokollergebnis:
Filter - Karte - Jeder; Filter - Karte - Jeder
Iterierbares Beispiel:
list.filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
Filter - Filter - Karte - Karte - Jeder - Jeder
Iterable
wird derjava.lang.Iterable
Schnittstelle auf dem zugeordnetJVM
und von häufig verwendeten Sammlungen wie List oder Set implementiert. Die Sammlungserweiterungsfunktionen auf diesen werden eifrig ausgewertet, was bedeutet, dass sie alle Elemente in ihrer Eingabe sofort verarbeiten und eine neue Sammlung zurückgeben, die das Ergebnis enthält.Hier ist ein einfaches Beispiel für die Verwendung der Erfassungsfunktionen, um die Namen der ersten fünf Personen in einer Liste abzurufen, deren Alter mindestens 21 Jahre beträgt:
val people: List<Person> = getPeople() val allowedEntrance = people .filter { it.age >= 21 } .map { it.name } .take(5)
Zielplattform: JVMRunning auf kotlin v. 1.3.61 Zunächst wird die Altersprüfung für jede einzelne Person in der Liste durchgeführt, wobei das Ergebnis in eine brandneue Liste aufgenommen wird. Dann wird die Zuordnung zu ihren Namen für jede Person durchgeführt, die nach dem Filteroperator verblieben ist und in einer weiteren neuen Liste endet (dies ist jetzt eine
List<String>
). Schließlich wird eine letzte neue Liste erstellt, die die ersten fünf Elemente der vorherigen Liste enthält.Im Gegensatz dazu ist Sequence ein neues Konzept in Kotlin, um eine träge bewertete Sammlung von Werten darzustellen. Die gleichen Sammlungserweiterungen sind für die
Sequence
Schnittstelle verfügbar , diese geben jedoch sofort Sequenzinstanzen zurück, die einen verarbeiteten Status des Datums darstellen, ohne jedoch tatsächlich Elemente zu verarbeiten. Um die Verarbeitung zu starten,Sequence
muss das mit einem Terminalbetreiber beendet werden. Dies ist im Grunde eine Aufforderung an die Sequenz, die Daten, die sie darstellt, in einer konkreten Form zu materialisieren. Beispiele sindtoList
,toSet
undsum
, um nur einige zu nennen. Wenn diese aufgerufen werden, wird nur die minimal erforderliche Anzahl von Elementen verarbeitet, um das angeforderte Ergebnis zu erzielen.Das Transformieren einer vorhandenen Sammlung in eine Sequenz ist ziemlich einfach. Sie müssen nur die
asSequence
Erweiterung verwenden. Wie oben erwähnt, müssen Sie auch einen Terminaloperator hinzufügen, da die Sequenz sonst niemals verarbeitet wird (wieder faul!).
val people: List<Person> = getPeople() val allowedEntrance = people.asSequence() .filter { it.age >= 21 } .map { it.name } .take(5) .toList()
Zielplattform: JVMRunning auf kotlin v. 1.3.61 In diesem Fall werden die Personeninstanzen in der Sequenz jeweils auf ihr Alter überprüft. Wenn sie bestanden werden, wird ihr Name extrahiert und dann zur Ergebnisliste hinzugefügt. Dies wird für jede Person in der ursprünglichen Liste wiederholt, bis fünf Personen gefunden wurden. Zu diesem Zeitpunkt gibt die Funktion toList eine Liste zurück, und der Rest der Personen in der
Sequence
wird nicht verarbeitet.Es gibt noch etwas Besonderes, zu dem eine Sequenz in der Lage ist: Sie kann unendlich viele Elemente enthalten. Vor diesem Hintergrund ist es sinnvoll, dass Operatoren so arbeiten, wie sie es tun - ein Operator mit einer unendlichen Sequenz könnte niemals zurückkehren, wenn er seine Arbeit eifrig erledigt hätte.
Als Beispiel ist hier eine Sequenz, die so viele Zweierpotenzen erzeugt, wie von ihrem Terminalbetreiber benötigt wird (wobei die Tatsache ignoriert wird, dass dies schnell überlaufen würde):
generateSequence(1) { n -> n * 2 } .take(20) .forEach(::println)
Mehr finden Sie hier .
Java
(meistensGuava
) Fans