Kedua antarmuka ini hanya mendefinisikan satu metode
public operator fun iterator(): Iterator<T>
Dokumentasi mengatakan Sequence
dimaksudkan untuk menjadi malas. Tapi bukankah Iterable
malas juga (kecuali didukung oleh a Collection
)?
Kedua antarmuka ini hanya mendefinisikan satu metode
public operator fun iterator(): Iterator<T>
Dokumentasi mengatakan Sequence
dimaksudkan untuk menjadi malas. Tapi bukankah Iterable
malas juga (kecuali didukung oleh a Collection
)?
Jawaban:
Perbedaan utama terletak pada semantik dan implementasi fungsi ekstensi stdlib untuk Iterable<T>
dan Sequence<T>
.
Sebab Sequence<T>
, fungsi ekstensi berjalan lambat jika memungkinkan, mirip dengan operasi perantara Java Streams . Misalnya, Sequence<T>.map { ... }
mengembalikan yang lain Sequence<R>
dan tidak benar-benar memproses item hingga operasi terminal seperti toList
atau fold
dipanggil.
Pertimbangkan kode ini:
val seq = sequenceOf(1, 2)
val seqMapped: Sequence<Int> = seq.map { print("$it "); it * it } // intermediate
print("before sum ")
val sum = seqMapped.sum() // terminal
Ini mencetak:
before sum 1 2
Sequence<T>
ditujukan untuk penggunaan yang lambat dan pipelining yang efisien ketika Anda ingin mengurangi pekerjaan yang dilakukan dalam operasi terminal sebanyak mungkin, sama dengan Java Streams. Namun, kemalasan menyebabkan beberapa overhead, yang tidak diinginkan untuk transformasi sederhana yang umum dari koleksi yang lebih kecil dan membuatnya kurang berkinerja.
Secara umum, tidak ada cara yang baik untuk menentukan kapan dibutuhkan, jadi di Kotlin stdlib kemalasan dibuat eksplisit dan diekstraksi ke Sequence<T>
antarmuka untuk menghindari penggunaannya di semua Iterable
secara default.
Karena Iterable<T>
, sebaliknya, fungsi ekstensi dengan semantik operasi perantara bekerja dengan penuh semangat, memproses item segera dan mengembalikan yang lain Iterable
. Misalnya, Iterable<T>.map { ... }
mengembalikan a List<R>
dengan hasil pemetaan di dalamnya.
Kode yang setara untuk Iterable:
val lst = listOf(1, 2)
val lstMapped: List<Int> = lst.map { print("$it "); it * it }
print("before sum ")
val sum = lstMapped.sum()
Ini mencetak:
1 2 before sum
Seperti yang dikatakan di atas, Iterable<T>
tidak malas secara default, dan solusi ini menunjukkan dirinya dengan baik: dalam banyak kasus ia memiliki lokalitas referensi yang baik sehingga memanfaatkan cache CPU, prediksi, prefetching, dll. Sehingga bahkan beberapa penyalinan koleksi masih berfungsi dengan baik cukup dan berkinerja lebih baik dalam kasus sederhana dengan koleksi kecil.
Jika Anda membutuhkan lebih banyak kontrol atas pipeline evaluasi, ada konversi eksplisit ke urutan malas dengan Iterable<T>.asSequence()
fungsi.
map
, filter
dan lainnya tidak membawa cukup informasi untuk memutuskan selain dari jenis kumpulan sumber, dan karena sebagian besar koleksi juga dapat diulang, itu bukan penanda yang baik untuk "malas" karena itu biasanya DI MANA SAJA. malas harus eksplisit agar aman.
Menyelesaikan jawaban hotkey:
Penting untuk memperhatikan bagaimana Sequence dan Iterable mengulangi seluruh elemen Anda:
Contoh urutan:
list.asSequence().filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
Hasil log:
filter - Peta - Masing-masing; filter - Peta - Masing-masing
Contoh Iterable:
list.filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
filter - filter - Peta - Peta - Masing - Masing
Iterable
dipetakan kejava.lang.Iterable
antarmuka diJVM
, dan diimplementasikan oleh koleksi yang umum digunakan, seperti List atau Set. Fungsi ekstensi koleksi ini dievaluasi dengan penuh semangat, yang berarti mereka semua segera memproses semua elemen dalam masukannya dan mengembalikan koleksi baru yang berisi hasilnya.Berikut adalah contoh sederhana penggunaan fungsi collection untuk mendapatkan nama dari lima orang pertama dalam daftar yang usianya setidaknya 21 tahun:
val people: List<Person> = getPeople() val allowedEntrance = people .filter { it.age >= 21 } .map { it.name } .take(5)
Platform target: JVMRunning di kotlin v. 1.3.61 Pertama, pemeriksaan usia dilakukan untuk setiap Orang dalam daftar, dengan hasil dimasukkan ke dalam daftar baru. Kemudian, pemetaan ke nama mereka dilakukan untuk setiap Orang yang tetap setelah operator filter, berakhir di daftar baru lainnya (ini sekarang a
List<String>
). Terakhir, ada satu daftar baru yang dibuat untuk memuat lima elemen pertama dari daftar sebelumnya.Sebaliknya, Sequence adalah konsep baru di Kotlin yang merepresentasikan kumpulan nilai yang dievaluasi dengan lambat. Ekstensi koleksi yang sama tersedia untuk
Sequence
antarmuka, tetapi ini segera mengembalikan instance Urutan yang mewakili status tanggal yang diproses, tetapi tanpa benar-benar memproses elemen apa pun. Untuk memulai pemrosesan,Sequence
harus diakhiri dengan operator terminal, ini pada dasarnya adalah permintaan ke Urutan untuk mewujudkan data yang diwakilinya dalam beberapa bentuk konkret. Contohnya termasuktoList
,,toSet
dansum
, untuk menyebutkan beberapa. Ketika ini dipanggil, hanya jumlah minimum elemen yang dibutuhkan yang akan diproses untuk menghasilkan hasil yang diminta.Mengubah koleksi yang ada menjadi Urutan sangatlah mudah, Anda hanya perlu menggunakan
asSequence
ekstensi. Seperti disebutkan di atas, Anda juga perlu menambahkan operator terminal, jika tidak, Urutan tidak akan pernah melakukan pemrosesan apa pun (sekali lagi, malas!).
val people: List<Person> = getPeople() val allowedEntrance = people.asSequence() .filter { it.age >= 21 } .map { it.name } .take(5) .toList()
Platform target: JVMRunning di kotlin v. 1.3.61 Dalam kasus ini, instance Person dalam Urutan masing-masing diperiksa usianya, jika lolos, namanya diekstrak, lalu ditambahkan ke daftar hasil. Ini diulangi untuk setiap orang dalam daftar awal hingga ada lima orang yang ditemukan. Pada titik ini, fungsi toList mengembalikan daftar, dan orang lain di dalam
Sequence
tidak diproses.Ada juga sesuatu yang ekstra yang mampu dilakukan oleh Urutan: dapat berisi item dalam jumlah tak terbatas. Dengan perspektif ini, masuk akal bahwa operator bekerja dengan cara yang mereka lakukan - operator pada urutan yang tidak terbatas tidak akan pernah bisa kembali jika melakukan pekerjaannya dengan penuh semangat.
Sebagai contoh, berikut adalah urutan yang akan menghasilkan kekuatan 2 sebanyak yang dibutuhkan oleh operator terminalnya (mengabaikan fakta bahwa ini akan cepat meluap):
generateSequence(1) { n -> n * 2 } .take(20) .forEach(::println)
Anda dapat menemukan lebih banyak di sini .
Java
(kebanyakanGuava
) penggemar