อินเทอร์เฟซทั้งสองนี้กำหนดวิธีการเดียวเท่านั้น
public operator fun iterator(): Iterator<T>
เอกสารบอกว่าSequence
ตั้งใจจะขี้เกียจ แต่ไม่Iterable
ขี้เกียจด้วย (เว้นแต่จะได้รับการสนับสนุนจาก a Collection
)?
อินเทอร์เฟซทั้งสองนี้กำหนดวิธีการเดียวเท่านั้น
public operator fun iterator(): Iterator<T>
เอกสารบอกว่าSequence
ตั้งใจจะขี้เกียจ แต่ไม่Iterable
ขี้เกียจด้วย (เว้นแต่จะได้รับการสนับสนุนจาก a Collection
)?
คำตอบ:
กุญแจสำคัญในความแตกต่างอยู่ในความหมายและการใช้งานฟังก์ชั่นการขยาย STDLIB สำหรับและIterable<T>
Sequence<T>
สำหรับSequence<T>
ฟังก์ชันส่วนขยายจะดำเนินการอย่างเฉื่อยชาหากเป็นไปได้เช่นเดียวกับการดำเนินการขั้นกลางของ Java Streams ตัวอย่างเช่นSequence<T>.map { ... }
ส่งคืนอีกรายการSequence<R>
และไม่ประมวลผลรายการจนกว่าจะมีการเรียกใช้งานเทอร์มินัลเหมือนtoList
หรือfold
ถูกเรียก
พิจารณารหัสนี้:
val seq = sequenceOf(1, 2)
val seqMapped: Sequence<Int> = seq.map { print("$it "); it * it } // intermediate
print("before sum ")
val sum = seqMapped.sum() // terminal
มันพิมพ์:
before sum 1 2
Sequence<T>
มีไว้สำหรับการใช้งานที่ขี้เกียจและการไปป์ไลน์ที่มีประสิทธิภาพเมื่อคุณต้องการลดงานที่ทำในการดำเนินการเทอร์มินัลให้มากที่สุดเท่าที่จะทำได้เช่นเดียวกับ Java Streams อย่างไรก็ตามความเกียจคร้านทำให้เกิดค่าใช้จ่ายบางส่วนซึ่งเป็นสิ่งที่ไม่พึงปรารถนาสำหรับการเปลี่ยนคอลเลคชันขนาดเล็กโดยทั่วไปและทำให้มีประสิทธิภาพน้อยลง
โดยทั่วไปไม่มีวิธีที่ดีในการระบุว่าเมื่อใดจำเป็นดังนั้นใน Kotlin stdlib ความเกียจคร้านจึงถูกทำให้ชัดเจนและดึงข้อมูลไปยังSequence<T>
อินเทอร์เฟซเพื่อหลีกเลี่ยงการใช้งานกับIterable
s ทั้งหมดโดยค่าเริ่มต้น
สำหรับIterable<T>
ในทางตรงกันข้าม, ฟังก์ชั่นการขยายกับกลางความหมายการดำเนินการทำงานกระหาย, Iterable
การประมวลผลรายการได้ทันทีและกลับอีก ตัวอย่างเช่นIterable<T>.map { ... }
ส่งคืน a List<R>
พร้อมกับผลลัพธ์การแมป
รหัสเทียบเท่าสำหรับทำซ้ำได้:
val lst = listOf(1, 2)
val lstMapped: List<Int> = lst.map { print("$it "); it * it }
print("before sum ")
val sum = lstMapped.sum()
สิ่งนี้พิมพ์ออกมา:
1 2 before sum
ตามที่กล่าวไว้ข้างต้นIterable<T>
ไม่ขี้เกียจโดยค่าเริ่มต้นและโซลูชันนี้แสดงให้เห็นได้ดี: ในกรณีส่วนใหญ่มีการอ้างอิงที่ดีดังนั้นการใช้ประโยชน์จากแคชของ CPU การคาดการณ์การดึงข้อมูลล่วงหน้าเป็นต้นเพื่อให้แม้แต่การคัดลอกคอลเลกชันหลาย ๆ ชุดก็ยังทำงานได้ดี เพียงพอและทำงานได้ดีขึ้นในกรณีง่ายๆที่มีคอลเล็กชันขนาดเล็ก
หากคุณต้องการการควบคุมมากขึ้นสำหรับไปป์ไลน์การประเมินผลมีการแปลงอย่างชัดเจนเป็นลำดับขี้เกียจพร้อมIterable<T>.asSequence()
ฟังก์ชัน
map
, filter
และอื่น ๆ ที่ไม่ได้ดำเนินการข้อมูลเพียงพอที่จะตัดสินใจอื่น ๆ นอกเหนือจากชนิดแหล่งที่มาของคอลเลกชันและตั้งแต่คอลเลกชันมากที่สุดนอกจากนี้ยังมี Iterable ที่ไม่ได้เป็นเครื่องหมายที่ดีสำหรับ "ขี้เกียจ" เพราะมันเป็น โดยทั่วไปทุกที่ ขี้เกียจต้องชัดเจนจึงจะปลอดภัย
การกรอกคำตอบของปุ่มลัด:
สิ่งสำคัญคือต้องสังเกตว่าลำดับและวนซ้ำได้อย่างไรในองค์ประกอบของคุณ:
ตัวอย่างลำดับ:
list.asSequence().filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
ผลการบันทึก:
ตัวกรอง - แผนที่ - แต่ละรายการ; ตัวกรอง - แผนที่ - แต่ละรายการ
ตัวอย่างที่ทำซ้ำได้:
list.filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
ตัวกรอง - ตัวกรอง - แผนที่ - แผนที่ - แต่ละรายการ
Iterable
ถูกแมปกับjava.lang.Iterable
อินเทอร์เฟซบนไฟล์JVM
และนำไปใช้โดยคอลเลกชันที่ใช้กันทั่วไปเช่นรายการหรือชุด ฟังก์ชันส่วนขยายคอลเลกชันในสิ่งเหล่านี้ได้รับการประเมินอย่างกระตือรือร้นซึ่งหมายความว่าทุกคนประมวลผลองค์ประกอบทั้งหมดในข้อมูลที่ป้อนทันทีและส่งคืนคอลเล็กชันใหม่ที่มีผลลัพธ์นี่คือตัวอย่างง่ายๆของการใช้ฟังก์ชันคอลเลกชันเพื่อรับชื่อของบุคคล 5 คนแรกในรายการที่มีอายุอย่างน้อย 21 ปี:
val people: List<Person> = getPeople() val allowedEntrance = people .filter { it.age >= 21 } .map { it.name } .take(5)
แพลตฟอร์มเป้าหมาย: JVMRunning บน kotlin v. 1.3.61 ขั้นแรกการตรวจสอบอายุจะทำสำหรับทุกคนในรายการโดยผลลัพธ์จะอยู่ในรายการใหม่เอี่ยม จากนั้นการแมปกับชื่อของพวกเขาจะทำสำหรับทุกคนที่ยังคงอยู่หลังจากตัวดำเนินการตัวกรองซึ่งจะลงท้ายด้วยรายการใหม่อีกรายการ (ตอนนี้คือ a
List<String>
) ในที่สุดก็มีรายการใหม่ล่าสุดที่สร้างขึ้นเพื่อให้มีห้าองค์ประกอบแรกของรายการก่อนหน้านี้ในทางตรงกันข้าม Sequence เป็นแนวคิดใหม่ใน Kotlin เพื่อแสดงการรวบรวมค่าที่ประเมินอย่างเฉื่อยชา ส่วนขยายคอลเลคชันเดียวกันพร้อมใช้งานสำหรับ
Sequence
อินเทอร์เฟซ แต่จะส่งคืนอินสแตนซ์ Sequence ทันทีที่แสดงสถานะประมวลผลของวันที่ แต่ไม่มีการประมวลผลองค์ประกอบใด ๆ ในการเริ่มต้นการประมวลผลSequence
จะต้องถูกยกเลิกด้วยตัวดำเนินการเทอร์มินัลซึ่งโดยพื้นฐานแล้วเป็นการร้องขอไปยังลำดับเพื่อทำให้ข้อมูลที่แสดงเป็นรูปธรรมในรูปแบบที่เป็นรูปธรรม ตัวอย่าง ได้แก่toList
,toSet
และsum
พูดถึงเพียงไม่กี่ เมื่อมีการเรียกสิ่งเหล่านี้จำนวนองค์ประกอบขั้นต่ำที่ต้องการเท่านั้นที่จะถูกประมวลผลเพื่อให้ได้ผลลัพธ์ที่ต้องการการเปลี่ยนคอลเลกชันที่มีอยู่ให้เป็นลำดับนั้นค่อนข้างตรงไปตรงมาคุณเพียงแค่ต้องใช้
asSequence
ส่วนขยาย ดังที่ได้กล่าวไว้ข้างต้นคุณต้องเพิ่มตัวดำเนินการเทอร์มินัลมิฉะนั้น Sequence จะไม่ทำการประมวลผลใด ๆ (อีกแล้วขี้เกียจ!)
val people: List<Person> = getPeople() val allowedEntrance = people.asSequence() .filter { it.age >= 21 } .map { it.name } .take(5) .toList()
แพลตฟอร์มเป้าหมาย: JVMRunning บน kotlin v.3.3.61 ในกรณีนี้อินสแตนซ์บุคคลในลำดับจะถูกตรวจสอบอายุของพวกเขาหากผ่านไปพวกเขาจะมีการแยกชื่อของพวกเขาแล้วเพิ่มลงในรายการผลลัพธ์ สิ่งนี้จะทำซ้ำสำหรับแต่ละคนในรายการเดิมจนกว่าจะมีคนพบห้าคน ณ จุดนี้ฟังก์ชัน toList จะส่งกลับรายการและคนที่เหลือในไฟล์
Sequence
จะไม่ถูกประมวลผลนอกจากนี้ยังมีสิ่งพิเศษที่ Sequence สามารถทำได้: สามารถบรรจุรายการได้ไม่ จำกัด จำนวน ด้วยมุมมองนี้จึงสมเหตุสมผลที่ตัวดำเนินการจะทำงานในแบบที่พวกเขาทำ - ตัวดำเนินการบนลำดับที่ไม่มีที่สิ้นสุดจะไม่สามารถย้อนกลับได้หากมันทำงานอย่างกระตือรือร้น
ตัวอย่างเช่นต่อไปนี้เป็นลำดับที่จะสร้างพาวเวอร์ของ 2 ได้มากตามที่ตัวดำเนินการเทอร์มินัลต้องการ (โดยไม่สนใจว่าสิ่งนี้จะล้นอย่างรวดเร็ว)
generateSequence(1) { n -> n * 2 } .take(20) .forEach(::println)
Java
(ส่วนใหญ่Guava
)