Thư viện tiêu chuẩn của Kotlin cung cấp một bộ công cụ toàn diện để quản lý các collections – các nhóm gồm một số lượng thay đổi các mục (có thể bằng 0) có cùng tầm quan trọng đối với vấn đề đang được giải quyết và được vận hành phổ biến.

Collections là một khái niệm chung cho hầu hết các ngôn ngữ lập trình, vì vậy, nếu bạn đã quen thuộc với các collections trong Java hoặc Python, bạn có thể bỏ qua phần giới thiệu này và chuyển sang các phần chi tiết.

Một collection thường chứa một số đối tượng (số này cũng có thể bằng 0) cùng loại. Các đối tượng trong một collection được gọi là phần tử hoặc mục. Ví dụ: tất cả sinh viên trong một khoa tạo thành một collection có thể được sử dụng để tính tuổi trung bình của họ. Các loại collections sau có liên quan đến Kotlin:

  • List  là một collection có thứ tự với quyền truy cập vào các phần tử theo chỉ số – số nguyên phản ánh vị trí của chúng. Các phần tử có thể xuất hiện nhiều lần trong một danh sách. Ví dụ điển hình về danh sách là một câu: đó là một nhóm từ, thứ tự của chúng mang ý nghĩa quan trọng và chúng có thể lặp lại.
  • Set là một collection các phần tử duy nhất. Nó phản ánh tính trừu tượng toán học của collection: một nhóm các đối tượng không lặp lại. Nói chung, thứ tự của các phần tử trong set không có nhiều ý nghĩa. Ví dụ, một bảng chữ cái là một set gồm các chữ cái.
  • Map (hoặc từ điền ) là một collection các cặp key- value. Các key là duy nhất và mỗi key ánh xạ đến chính xác một value. Các value có thể trùng lặp. Map rất hữu ích để lưu trữ các kết nối logic giữa các đối tượng, ví dụ, ID của nhân viên và vị trí của họ.

Kotlin cho phép bạn thao tác các collections độc lập với loại đối tượng chính xác được lưu trữ trong chúng. Nói cách khác, bạn thêm một String vào danh sách Strings giống như cách bạn làm với Ints hoặc một class do người dùng xác định. Vì vậy, Thư viện tiêu chuẩn của Kotlin cung cấp các interface, class và các hàm để tạo, điền và quản lý các collections thuộc bất kỳ kiểu nào.

Các interface collections và các chức năng liên quan nằm trong gói kotlin.collections. Hãy cùng tìm hiểu tổng quan về nội dung của nó.

1. Các loại collections

Thư viện tiêu chuẩn của Kotlin triển khai các kiểu collection cơ bản: collection, list và map. Một cặp interface đại diện cho từng loại collection:

  • Một interface chỉ đọc  cung cấp các phép toán để truy cập vào các phần tử của collections.
  • Một interface có thể thay đổi mở rộng interface chỉ đọc tương ứng với các phép tính ghi như: thêm vào, loại bỏ, và cập nhật các phần tử của nó.

Lưu ý rằng việc thay đổi một tập hợp có thể thay đổi không yêu cầu nó phải là một var: phép toán ghi sửa đổi cùng một đối tượng collection có thể thay đổi, vì vậy tham chiếu không thay đổi. Mặc dù, nếu bạn cố gắng gán lại một val collection, bạn sẽ gặp phải lỗi biên dịch.

val numbers = mutableListOf("one", "two", "three", "four")
numbers.add("five")   // this is OK    
//numbers = mutableListOf("six", "seven")      // compilation error

Các kiểu tập hợp chỉ đọc là biến . Điều này có nghĩa là, nếu một class Rectangle kế thừa từ Shape, bạn có thể sử dụng một List<Rectangle> bất kỳ nơi nào mà List<Shape> được yêu cầu. Nói cách khác, các kiểu tập hợp có cùng mối quan hệ kiểu con với kiểu phần tử. Map đồng biến trên loại giá trị, nhưng không đồng biến với kiểu của key.

Ngược lại, các tập hợp có thể thay đổi không đồng biến; nếu không, điều này sẽ dẫn đến lỗi thời gian chạy. Nếu MutableList<Rectangle> là một kiểu con của MutableList<Shape>, bạn có thể chèn các kiểu kế thừa Shape khác (ví dụ Circle:) vào đó, do đó vi phạm kiểu của đối số Rectangle .

Dưới đây là sơ đồ của các collection interfaces trong Kotlin:

Hãy xem qua các interface và cách triển khai của chúng.

2. Collection

Collection<T> là interface cha của hệ thống phân cấp collection. Interface này đại diện cho hành vi phổ biến của một collection chỉ đọc: truy xuất kích thước, kiểm tra tư cách thành viên của mục, v.v. Collection kế thừa từ interface Iterable<T>, interface này xác định các phép toán lặp qua các phần tử. Bạn có thể sử dụng Collection làm tham số của một hàm áp dụng cho các loại collection khác nhau. Đối với các trường hợp cụ thể hơn, hãy sử dụng các inheritors của Collection: ListSet.

fun printAll(strings: Collection<String>) {
        for(s in strings) print("$s ")
        println()
    }
    
fun main() {
    val stringList = listOf("one", "two", "one")
    printAll(stringList)
    
    val stringSet = setOf("one", "two", "three")
    printAll(stringSet)
}

MutableCollection là một  Collection với các phép toán ghi, như là cộng và trừ.

fun List<String>.getShortWordsTo(shortWords: MutableList<String>, maxLength: Int) {
    this.filterTo(shortWords) { it.length <= maxLength }
    // throwing away the articles
    val articles = setOf("a", "A", "an", "An", "the", "The")
    shortWords -= articles
}

fun main() {
    val words = "A long time ago in a galaxy far far away".split(" ")
    val shortWords = mutableListOf<String>()
    words.getShortWordsTo(shortWords, 3)
    println(shortWords)
}

3. List

List<T> lưu trữ các phần tử theo một thứ tự cụ thể và cung cấp quyền truy cập được lập chỉ mục vào chúng. Các chỉ số bắt đầu từ số 0- chỉ số của phần tử đầu tiên – và chuyển đến lastIndex là (list.size – 1).

val numbers = listOf("one", "two", "three", "four")
println("Number of elements: ${numbers.size}")
println("Third element: ${numbers.get(2)}")
println("Fourth element: ${numbers[3]}")
println("Index of element \"two\" ${numbers.indexOf("two")}")

Các phần tử trong danh sách (bao gồm cả null) có thể trùng lặp: một danh sách có thể chứa bất kỳ số lượng đối tượng bằng nhau hoặc số lần xuất hiện của một đối tượng. Hai list được coi là bằng nhau nếu chúng có cùng kích thước và các phần tử bằng nhau về cấu trúc ở cùng vị trí.

val bob = Person("Bob", 31)
val people = listOf(Person("Adam", 20), bob, bob)
val people2 = listOf(Person("Adam", 20), Person("Bob", 31), bob)
println(people == people2)
bob.age = 32
println(people == people2)

MutableList<T> là một List với các phép tính ghi danh sách cụ thể, ví dụ, thêm hoặc bớt một phần tử tại một vị trí cụ thể.

val numbers = mutableListOf(1, 2, 3, 4)
numbers.add(5)
numbers.removeAt(1)
numbers[0] = 0
numbers.shuffle()
println(numbers)

Như bạn thấy, ở một số khía cạnh, danh sách rất giống với mảng. Tuy nhiên, có một điểm khác biệt quan trọng: kích thước của một mảng được xác định khi khởi tạo và không bao giờ thay đổi; trong khi đó, một danh sách không có kích thước xác định trước; kích thước của danh sách có thể được thay đổi do kết quả của hoạt động ghi: thêm, cập nhật hoặc loại bỏ phần tử.

Trong Kotlin, triển khai mặc định của List là ArrayList, bạn có thể coi nó như là một mảng có thể biến đổi kịch thước.

4. Set

Set<T> lưu trữ các phần tử duy nhất; thứ tự của chúng nói chung là không xác định. Phần tử null cũng là duy nhất: một Set chỉ có thể chứa một null. Hai set bằng nhau nếu chúng có cùng kích thước và đối với mỗi phần tử của set  có một phần tử bằng nhau trong set  kia.

val numbers = setOf(1, 2, 3, 4)
println("Number of elements: ${numbers.size}")
if (numbers.contains(1)) println("1 is in the set")

val numbersBackwards = setOf(4, 3, 2, 1)
println("The sets are equal: ${numbers == numbersBackwards}")

MutableSet là một Set với các phép tính ghi từ MutableCollection.

Triển khai mặc định của Set- LinkedHashSet– bảo toàn thứ tự chèn các phần tử. Do đó, các hàm dựa vào thứ tự, chẳng hạn như first()hoặc last(), trả về kết quả có thể dự đoán được trên các set như vậy.

val numbers = setOf(1, 2, 3, 4)  // LinkedHashSet is the default implementation
val numbersBackwards = setOf(4, 3, 2, 1)

println(numbers.first() == numbersBackwards.first())
println(numbers.first() == numbersBackwards.last())

Một triển khai thay thế – HashSet– không đề cập về thứ tự các phần tử, vì vậy việc gọi các hàm như vậy trên nó sẽ trả về kết quả không thể đoán trước. Tuy nhiên, HashSet yêu cầu ít bộ nhớ hơn để lưu trữ cùng một số lượng phần tử.

5. Map

Map<K, V> không phải là inheritor của Collection interface; tuy nhiên, đó cũng là một loại collection trong Kotlin. Map lưu trữ các cặp key- value (hoặc các mục nhập ); các key là duy nhất, nhưng các key khác nhau có thể được ghép nối với các value bằng nhau. Các Map interface cung cấp các chức năng cụ thể, chẳng hạn như truy cập vào valueị theo key, tìm kiếm key và value, và vân vân.

val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)

println("All keys: ${numbersMap.keys}")
println("All values: ${numbersMap.values}")
if ("key2" in numbersMap) println("Value by key \"key2\": ${numbersMap["key2"]}")    
if (1 in numbersMap.values) println("The value 1 is in the map")
if (numbersMap.containsValue(1)) println("The value 1 is in the map") // same as previous

Hai map chứa các cặp bằng nhau thì được gọi là hai map bằng nhau bất kể thứ tự cặp.

val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)    
val anotherMap = mapOf("key2" to 2, "key1" to 1, "key4" to 1, "key3" to 3)

println("The maps are equal: ${numbersMap == anotherMap}")

MutableMap là một Map với các phép toán ghi map, ví dụ: bạn có thể thêm một cặp key- value mới hoặc cập nhật value được liên kết với key đã cho.

val numbersMap = mutableMapOf("one" to 1, "two" to 2)
numbersMap.put("three", 3)
numbersMap["one"] = 11

println(numbersMap)

Triển khai mặc định của Map- LinkedHashMap– duy trì thứ tự chèn các phần tử khi lặp lại map. Đổi lại, một triển khai thay thế – HashMap– không đề cập về thứ tự các phần tử.

Tài liệu từ cafedev:

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!