Cả hai giao diện này chỉ xác định một phương thức
public operator fun iterator(): Iterator<T>
Tài liệu nói Sequence
có nghĩa là lười biếng. Nhưng không phải là Iterable
lười biếng (trừ khi được hỗ trợ bởi a Collection
)?
Cả hai giao diện này chỉ xác định một phương thức
public operator fun iterator(): Iterator<T>
Tài liệu nói Sequence
có nghĩa là lười biếng. Nhưng không phải là Iterable
lười biếng (trừ khi được hỗ trợ bởi a Collection
)?
Câu trả lời:
Sự khác biệt chính nằm ở ngữ nghĩa và việc triển khai các hàm mở rộng stdlib cho Iterable<T>
và Sequence<T>
.
Đối với Sequence<T>
, các chức năng mở rộng thực hiện một cách lười biếng nếu có thể, tương tự như các hoạt động trung gian của Java Streams . Ví dụ, Sequence<T>.map { ... }
trả về một giá trị khác Sequence<R>
và không thực sự xử lý các mục cho đến khi một hoạt động đầu cuối như toList
hoặc fold
được gọi.
Hãy xem xét mã này:
val seq = sequenceOf(1, 2)
val seqMapped: Sequence<Int> = seq.map { print("$it "); it * it } // intermediate
print("before sum ")
val sum = seqMapped.sum() // terminal
Nó in:
before sum 1 2
Sequence<T>
nhằm mục đích sử dụng lười biếng và tổng hợp hiệu quả khi bạn muốn giảm công việc thực hiện trong các hoạt động đầu cuối nhiều nhất có thể, giống như Java Streams. Tuy nhiên, sự lười biếng tạo ra một số chi phí, điều không mong muốn đối với các phép biến đổi đơn giản phổ biến của các bộ sưu tập nhỏ hơn và làm cho chúng kém hiệu quả hơn.
Nói chung, không có cách nào tốt để xác định thời điểm cần thiết, vì vậy trong Kotlin, stdlib lười biếng được thực hiện rõ ràng và trích xuất vào Sequence<T>
giao diện để tránh sử dụng nó trên tất cả các Iterable
s theo mặc định.
Đối với Iterable<T>
, ngược lại, các chức năng mở rộng với trung ngữ nghĩa hoạt động làm việc hăng hái, chế biến các mặt hàng ngay lập tức và trở lại khác Iterable
. Ví dụ, Iterable<T>.map { ... }
trả về a List<R>
với kết quả ánh xạ trong đó.
Mã tương đương cho có thể lặp lại:
val lst = listOf(1, 2)
val lstMapped: List<Int> = lst.map { print("$it "); it * it }
print("before sum ")
val sum = lstMapped.sum()
Điều này in ra:
1 2 before sum
Như đã nói ở trên, Iterable<T>
theo mặc định là không lười biếng và giải pháp này cho thấy bản thân nó tốt: trong hầu hết các trường hợp, nó có vị trí tham chiếu tốt, do đó tận dụng bộ nhớ cache của CPU, dự đoán, tìm nạp trước, v.v. để thậm chí sao chép nhiều bộ sưu tập vẫn hoạt động tốt đủ và hoạt động tốt hơn trong các trường hợp đơn giản với các bộ sưu tập nhỏ.
Nếu bạn cần kiểm soát nhiều hơn đối với quy trình đánh giá, có một chuyển đổi rõ ràng thành chuỗi lười biếng có Iterable<T>.asSequence()
chức năng.
map
, filter
và những người khác không mang đủ thông tin để quyết định khác hơn là từ các loại bộ sưu tập nguồn, và vì hầu hết các bộ sưu tập cũng là Iterable, đó không phải là một dấu hiệu tốt cho "lười biếng" vì nó là thường là MỌI NƠI. lười biếng phải được rõ ràng để được an toàn.
Hoàn thành câu trả lời của phím nóng:
Điều quan trọng cần lưu ý là cách Sequence và Iterable lặp lại trong các phần tử của bạn:
Ví dụ về trình tự:
list.asSequence().filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
Kết quả ghi:
bộ lọc - Bản đồ - Mỗi; bộ lọc - Bản đồ - Mỗi
Ví dụ có thể lặp lại:
list.filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
bộ lọc - bộ lọc - Bản đồ - Bản đồ - Mỗi - Mỗi
Iterable
được ánh xạ tớijava.lang.Iterable
giao diện trênJVM
và được triển khai bởi các bộ sưu tập thường dùng, như Danh sách hoặc Tập hợp. Các hàm mở rộng bộ sưu tập trên các hàm này được đánh giá một cách háo hức, có nghĩa là tất cả chúng đều xử lý ngay lập tức tất cả các phần tử trong đầu vào của chúng và trả về một tập hợp mới chứa kết quả.Dưới đây là một ví dụ đơn giản về việc sử dụng các hàm tập hợp để lấy tên của năm người đầu tiên trong danh sách có tuổi ít nhất là 21:
val people: List<Person> = getPeople() val allowedEntrance = people .filter { it.age >= 21 } .map { it.name } .take(5)
Nền tảng mục tiêu: JVMRunning trên kotlin v. 1.3.61 Đầu tiên, việc kiểm tra độ tuổi được thực hiện cho từng Người trong danh sách, với kết quả được đưa vào một danh sách hoàn toàn mới. Sau đó, ánh xạ đến tên của họ được thực hiện cho mọi Người vẫn ở sau toán tử bộ lọc, kết thúc trong một danh sách mới khác (đây là một
List<String>
). Cuối cùng, có một danh sách mới cuối cùng được tạo để chứa năm phần tử đầu tiên của danh sách trước đó.Ngược lại, Trình tự là một khái niệm mới trong Kotlin để đại diện cho một tập hợp các giá trị được đánh giá một cách lười biếng. Các phần mở rộng bộ sưu tập tương tự có sẵn cho
Sequence
giao diện, nhưng các phần mở rộng này ngay lập tức trả về các phiên bản Trình tự đại diện cho trạng thái đã xử lý của ngày, nhưng không thực sự xử lý bất kỳ phần tử nào. Để bắt đầu xử lý,Sequence
nó phải được kết thúc bằng một toán tử đầu cuối, về cơ bản đây là một yêu cầu đối với Trình tự để hiện thực hóa dữ liệu mà nó đại diện ở một số dạng cụ thể. Ví dụ bao gồmtoList
,toSet
vàsum
, chỉ đề cập đến một số. Khi chúng được gọi, chỉ số phần tử bắt buộc tối thiểu sẽ được xử lý để tạo ra kết quả được yêu cầu.Việc chuyển đổi một bộ sưu tập hiện có thành một Chuỗi khá đơn giản, bạn chỉ cần sử dụng
asSequence
tiện ích mở rộng. Như đã đề cập ở trên, bạn cũng cần phải thêm toán tử đầu cuối, nếu không Sequence sẽ không bao giờ thực hiện bất kỳ xử lý nào (một lần nữa, lười biếng!).
val people: List<Person> = getPeople() val allowedEntrance = people.asSequence() .filter { it.age >= 21 } .map { it.name } .take(5) .toList()
Nền tảng mục tiêu: JVMRunning trên kotlin v. 1.3.61 Trong trường hợp này, từng cá thể Người trong Chuỗi được kiểm tra tuổi của họ, nếu họ vượt qua, họ sẽ được trích xuất tên và sau đó được thêm vào danh sách kết quả. Điều này được lặp lại cho từng người trong danh sách ban đầu cho đến khi có năm người được tìm thấy. Tại thời điểm này, hàm toList trả về một danh sách và những người còn lại trong danh sách
Sequence
không được xử lý.Ngoài ra còn có một thứ bổ sung mà Sequence có khả năng: nó có thể chứa vô số mục. Với quan điểm này, có nghĩa là các toán tử làm việc theo cách họ làm - một toán tử trên một dãy vô hạn có thể không bao giờ quay lại nếu nó thực hiện công việc của mình một cách háo hức.
Ví dụ: đây là một chuỗi sẽ tạo ra bao nhiêu lũy thừa của 2 theo yêu cầu của toán tử đầu cuối của nó (bỏ qua thực tế là điều này sẽ nhanh chóng tràn):
generateSequence(1) { n -> n * 2 } .take(20) .forEach(::println)
Bạn có thể tìm thêm ở đây .
Java
(hầu hếtGuava
) người hâm mộ