Chào mừng độc giả Cafedev! Trong thế giới lập trình hiện đại, Kotlin mang đến một tiềm năng mạnh mẽ với tính năng “Các lớp giá trị Inline”. Điều này không chỉ giúp tối ưu hóa hiệu suất mà còn mở ra những khả năng mới trong quá trình phát triển. Cafedev sẽ cùng chúng ta khám phá sâu hơn về tính năng này, đồng hành trong hành trình khám phá thế giới Kotlin và những tiện ích mà nó mang lại. Hãy cùng đắm chìm vào thế giới mới của Kotlin với chủ đề “Inline value classes”!”

Đôi khi, việc bọc một giá trị trong một lớp để tạo ra một kiểu cụ thể cho miền cụ thể là hữu ích. Tuy nhiên, điều này giới thiệu một chi phí thời gian chạy do có thêm cấp phát heap. Hơn nữa, nếu kiểu được bọc là nguyên thủy, tổn thất hiệu suất là đáng kể, vì các kiểu nguyên thủy thường được tối ưu hóa mạnh mẽ bởi thời gian chạy, trong khi các lớp bọc của chúng không nhận được xử lý đặc biệt nào.
Để giải quyết những vấn đề như vậy, Kotlin giới thiệu một loại lớp đặc biệt được gọi là lớp giá trị trong đường (inline class). Lớp giá trị là một phần của các lớp dựa trên giá trị. Chúng không có một định danh và chỉ có thể giữ các giá trị.
Để khai báo một lớp giá trị, sử dụng từ khóa value trước tên của lớp:

value class Password(private val s: String)

Để khai báo một lớp giá trị cho JVM backend, sử dụng từ khóa value cùng với chú thích @JvmInline trước khai báo lớp:

// For JVM backends
@JvmInline
value class Password(private val s: String)

Một lớp giá trị phải có một thuộc tính duy nhất được khởi tạo trong hàm tạo chính. Tại thời gian chạy, các trường hợp của lớp giá trị sẽ được biểu diễn bằng cách sử dụng thuộc tính duy nhất này (xem chi tiết về biểu diễn tại thời gian chạy dưới đây):

// No actual instantiation of class 'Password' happens
// At runtime 'securePassword' contains just 'String'
val securePassword = Password("Don't try this in production")

Đây là đặc điểm chính của các lớp giá trị, đã truyền cảm hứng cho cái tên inline: dữ liệu của lớp được nhúng vào các sử dụng của nó (tương tự như nội dung của các hàm inline được nhúng vào các điểm gọi).

1. Thành viên

Các lớp giá trị hỗ trợ một số chức năng của các lớp thông thường. Đặc biệt, chúng được phép khai báo thuộc tính và hàm, có một khối initcác hàm tạo thứ cấp:

@JvmInline
value class Person(private val fullName: String) {
    init {
        require(fullName.isNotEmpty()) {
            "Full name shouldn't be empty"
        }
    }

    constructor(firstName: String, lastName: String) : this("$firstName $lastName") {
        require(lastName.isNotBlank()) {
            "Last name shouldn't be empty"
        }
    }

    val length: Int
        get() = fullName.length

    fun greet() {
        println("Hello, $fullName")
    }
}

fun main() {
    val name1 = Person("Kotlin", "Mascot")
    val name2 = Person("Kodee")
    name1.greet() // the `greet()` function is called as a static method
    println(name2.length) // property getter is called as a static method
}

Thuộc tính của lớp giá trị không thể có trường hỗ trợ. Chúng chỉ có thể có các thuộc tính tính toán đơn giản (không có lateinit/thuộc tính giao).

2. Kế thừa

Các lớp giá trị được phép kế thừa từ các giao diện:

interface Printable {
    fun prettyPrint(): String
}

@JvmInline
value class Name(val s: String) : Printable {
    override fun prettyPrint(): String = "Let's $s!"
}

fun main() {
    val name = Name("Kotlin")
    println(name.prettyPrint()) // Still called as a static method
}

Nó bị cấm đối với các lớp giá trị tham gia vào một phân cấp lớp. Điều này có nghĩa là các lớp giá trị không thể mở rộng từ các lớp khác và luôn luôn là final.

3. Biểu diễn

Trong mã nguồn được tạo ra, trình biên dịch Kotlin giữ một bọc cho mỗi lớp inline. Các trường hợp của lớp inline có thể được biểu diễn ở thời gian chạy dưới dạng bọc hoặc dưới dạng kiểu cơ bản. Điều này tương tự như cách Int có thể được biểu diễn dưới dạng int nguyên thủy hoặc dưới dạng bọc Integer.
Trình biên dịch Kotlin thường ưu tiên sử dụng kiểu cơ bản thay vì lớp bọc để tạo mã hiệu suất và tối ưu nhất. Tuy nhiên, đôi khi cần giữ lại các lớp bọc. Một quy tắc thông thường là lớp inline sẽ được đặt vào hộp khi chúng được sử dụng như một kiểu khác.

interface I

@JvmInline
value class Foo(val i: Int) : I

fun asInline(f: Foo) {}
fun <T> asGeneric(x: T) {}
fun asInterface(i: I) {}
fun asNullable(i: Foo?) {}

fun <T> id(x: T): T = x

fun main() {
    val f = Foo(42)

    asInline(f)    // unboxed: used as Foo itself
    asGeneric(f)   // boxed: used as generic type T
    asInterface(f) // boxed: used as type I
    asNullable(f)  // boxed: used as Foo?, which is different from Foo

    // below, 'f' first is boxed (while being passed to 'id') and then unboxed (when returned from 'id')
    // In the end, 'c' contains unboxed representation (just '42'), as 'f'
    val c = id(f)
}

Bởi vì lớp inline có thể được biểu diễn cả như giá trị cơ bản và như một lớp bọc, đồng bằng theo tham chiếu trở nên vô nghĩa đối với chúng và do đó bị cấm.
Lớp inline cũng có thể có một tham số kiểu chung làm kiểu cơ bản. Trong trường hợp này, trình biên dịch ánh xạ nó thành Any?hoặc, nói chung, là đến giới hạn trên của tham số kiểu.

@JvmInline
value class UserId<T>(val value: T)

fun compute(s: UserId<String>) {} // compiler generates fun compute-<hashcode>(s: Any?)

3.1 Mangling

Vì lớp inline được biên dịch thành kiểu cơ bản của nó, điều này có thể dẫn đến các lỗi kỳ lạ khác nhau, ví dụ như xung đột không mong muốn giữa các chữ ký nền tảng:

@JvmInline
value class UInt(val x: Int)

// Represented as 'public final void compute(int x)' on the JVM
fun compute(x: Int) { }

// Also represented as 'public final void compute(int x)' on the JVM!
fun compute(x: UInt) { }

Để giảm nhẹ vấn đề này, các hàm sử dụng lớp inline được mangled bằng cách thêm một số hashcode ổn định vào tên hàm. Do đó, fun compute(x: UInt) sẽ được biểu diễn như public final void compute-(int x), giải quyết vấn đề xung đột.

3.2 Gọi từ mã Java

Bạn có thể gọi các hàm chấp nhận lớp inline từ mã Java. Để làm điều này, bạn nên tắt mangling thủ công: thêm chú thích @JvmName trước khai báo hàm:

@JvmInline
value class UInt(val x: Int)

fun compute(x: Int) { }

@JvmName("computeUInt")
fun compute(x: UInt) { }

4. Lớp inline so với type aliases

Một cách nhìn sơ bộ, lớp inline có vẻ rất tương tự type aliases. Thực sự, cả hai đều giống nhau khi giới thiệu một kiểu mới và cả hai sẽ được biểu diễn dưới dạng kiểu cơ bản ở thời gian chạy.
Tuy nhiên, sự khác biệt quan trọng là các type alias có thể gán tương thích với kiểu cơ bản của chúng (và với các type alias khác có cùng kiểu cơ bản), trong khi các lớp inline không có tính tương thích gán.
Nói cách khác, các lớp inline giới thiệu một loại thực sự mới, ngược lại với các type alias chỉ giới thiệu một tên thay thế (alias) cho một loại đã tồn tại:

typealias NameTypeAlias = String

@JvmInline
value class NameInlineClass(val s: String)

fun acceptString(s: String) {}
fun acceptNameTypeAlias(n: NameTypeAlias) {}
fun acceptNameInlineClass(p: NameInlineClass) {}

fun main() {
    val nameAlias: NameTypeAlias = ""
    val nameInlineClass: NameInlineClass = NameInlineClass("")
    val string: String = ""

    acceptString(nameAlias) // OK: pass alias instead of underlying type
    acceptString(nameInlineClass) // Not OK: can't pass inline class instead of underlying type

    // And vice versa:
    acceptNameTypeAlias(string) // OK: pass underlying type instead of alias
    acceptNameInlineClass(string) // Not OK: can't pass underlying type instead of inline class
}

5. Lớp inline và delegation

Việc triển khai bằng cách giao phó cho giá trị được nhúng của lớp được nhúng được phép với các giao diện:

interface MyInterface {
    fun bar()
    fun foo() = "foo"
}

@JvmInline
value class MyInterfaceWrapper(val myInterface: MyInterface) : MyInterface by myInterface

fun main() {
    val my = MyInterfaceWrapper(object : MyInterface {
        override fun bar() {
            // body
        }
    })
    println(my.foo()) // prints "foo"
}

Cảm ơn bạn đã dành thời gian để cùng Cafedev khám phá tính năng “Các lớp giá trị Inline” trong Kotlin. Hy vọng rằng thông tin chúng tôi chia sẻ đã là nguồn động viên và kiến thức hữu ích. Đừng ngần ngại tiếp tục đồng hành với Cafedev trên hành trình lập trình Kotlin, nơi mà sự sáng tạo và hiệu suất gặp gỡ. Hãy khám phá thêm về những ưu điểm và ứng dụng của “Inline value classes” để nâng cao chất lượng mã nguồn và trải nghiệm lập trình của bạn. Đến gặp lại trên Cafedev!

Các nguồn kiến thức MIỄN PHÍ VÔ GIÁ từ cafedev tại đây

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!