Inline function trong Kotlin: định nghĩa, cú pháp và ứng dụng

Tác giả: IT Sectr Đã đăng: 2026-06-21 Thời gian đọc: 9 phút

inline function — cơ chế Kotlin trong đó thân hàm được thay thế trực tiếp tại mỗi vị trí gọi vào thời điểm biên dịch. Điều này loại bỏ overhead từ việc tạo các lớp ẩn danh và đối tượng cho tham số lambda. Theo Kotlin Documentation, 2025, từ khóa inline đặc biệt hiệu quả cho các hàm bậc cao, nơi mỗi lambda mà không có nội tuyến sẽ tạo ra một đối tượng FunctionN riêng, làm nặng bộ thu gom rác.

Các điểm chính

  • Hàm inline — hàm có thân được nội tuyến tại vị trí gọi vào thời điểm biên dịch
  • Giảm overhead — loại bỏ việc tạo lớp ẩn danh và đối tượng cho lambda
  • Nonlocal return — lambda bên trong hàm inline có thể return từ hàm bên ngoài
  • noinline — ngăn nội tuyến các tham số lambda cụ thể
  • crossinline — cho phép nonlocal return nhưng cấm return từ ngữ cảnh hàm inline

Inline function trong Kotlin là gì?

Hàm inline là hàm được đánh dấu bằng từ khóa inline. Trình biên dịch Kotlin không tạo bytecode riêng với lời gọi cho nó — thay vào đó, nó sao chép trực tiếp thân hàm vào mỗi vị trí gọi. Mục tiêu chính là tối ưu hóa các hàm bậc cao chấp nhận biểu thức lambda, vì mỗi lambda trong tình huống bình thường tạo ra một đối tượng lớp Function ẩn danh.

Theo JetBrains Tech Blog (2024), sử dụng hàm inline trong Kotlin có thể giảm số lượng đối tượng được tạo ra từ 40–60% trong các hàm sử dụng lambda nhiều. Trong vòng lặp và các hoạt động tải cao (sắp xếp, lọc bộ sưu tập), điều này mang lại lợi ích hiệu suất có thể đo lường được.

Không có inline, mỗi lambda được biên dịch thành một lớp ẩn danh (hoặc một thể hiện của giao diện chức năng tổng hợp). Đối với lambda thu thập biến, các đối tượng bao bọc bổ sung được tạo ra. Hàm inline loại bỏ tất cả các đối tượng này tại thời điểm biên dịch, thay thế chúng bằng mã trực tiếp truy cập biến cục bộ mà không cần bao bọc.

Chỉ sử dụng inline cho các hàm có tham số lambda — trình biên dịch Kotlin tự cảnh báo nếu inline không mang lại lợi ích.

Cú pháp inline function và cách hoạt động

Chỉ cần thêm từ khóa inline trước khai báo hàm. Trình biên dịch tự động thay thế thân hàm tại các vị trí gọi. Bản thân hàm tiếp tục tồn tại trong bytecode cho các trường hợp không được gọi trực tiếp (ví dụ: từ mã Java).

kotlin
inline fun Int.repeatAction(action: (Int) -> Unit) {
    for (i in 0 until this) {
        action(i)
    }
}

// Gọi — mã lambda được nội tuyến vào thân hàm
5.repeatAction { index ->
    println("Index: $index")
}

Sau khi biên dịch, mã trên sẽ tương đương với:

kotlin
// Điều gì xảy ra sau khi nội tuyến (một cách sơ đồ):
val $this = 5
for (i in 0 until $this) {
    println("Index: $i")
}

Không có đối tượng nào được tạo cho lambda — mã hành động được thực thi trực tiếp. Đây là bản chất của tối ưu hóa: thay vì gọi Function.invoke() — một sự chèn trực tiếp mã với thân lambda.

Xác minh thông qua dịch ngược

Để xác minh nội tuyến, hãy mở Tools > Kotlin > Show Kotlin Bytecode trong IntelliJ IDEA và nhấp vào Decompile. Bạn sẽ thấy rằng thay vì gọi repeatAction với một lambda, một sự chèn trực tiếp thân hàm với vòng lặp for được tạo ra.

Vấn đề overhead lambda và giải pháp

Mỗi lambda trong Kotlin được biên dịch thành một trong ba biến thể. Thứ nhất — nếu lambda không thu thập biến, nó trở thành một phương thức tĩnh của lớp nơi nó được khai báo. Thứ hai — nếu nó thu thập một biến, một lớp ẩn danh được tạo ra. Thứ ba — nếu nó thu thập nhiều biến, một lớp ẩn danh với các trường cho mỗi biến được thu thập được tạo ra.

Loại lambdaKhông có inlineCó inline
Không thu thậpMột phương thức tĩnh (tái sử dụng)Nội tuyến hoàn toàn, không gọi
Thu thập 1 biếnLớp ẩn danh (một đối tượng)Nội tuyến hoàn toàn, không đối tượng
Thu thập N biếnLớp ẩn danh với N trườngNội tuyến hoàn toàn, không đối tượng
Đệ quyGọi bình thườnginline bị cấm

Theo Android Performance Patterns (Google, 2024), trong các ứng dụng sử dụng nhiều bộ sưu tập (lọc, sắp xếp, nhóm), hàm inline giảm cấp phát từ 25–35%. Hiệu quả đặc biệt rõ rệt trong Jetpack Compose, nơi mỗi thay đổi trạng thái kích hoạt sự tái tổ hợp với nhiều lambda.

Nonlocal return và hạn chế

Lambda trong một hàm thông thường không thể return từ hàm bên ngoài — chỉ có return cục bộ từ chính lambda (qua return@label). Trong một hàm inline, lambda được nội tuyến vào thân hàm gọi, do đó nonlocal return trở nên khả thi: return bên trong lambda kết thúc hàm bên ngoài.

kotlin
inline fun findFirst(
    items: List<Int>,
    predicate: (Int) -> Boolean
): Int {
    for (item in items) {
        if (predicate(item)) {
            return item
        }
    }
    return -1
}

fun processNumbers() {
    val numbers = listOf(1, 2, 3)
    val firstEven = findFirst(numbers) { it % 2 == 0 }
    // return trong lambda sẽ trả về null từ processNumbers()
}

Nonlocal return thuận tiện cho việc kết thúc sớm, nhưng có thể dẫn đến lỗi. Nếu lambda được sử dụng trong ngữ cảnh không cục bộ (được lưu trong biến), nonlocal return sẽ gây ra RuntimeException. Trình biên dịch Kotlin đưa ra cảnh báo khi cố gắng lưu trữ như vậy.

noinline và crossinline: kiểm soát nội tuyến

Khi một hàm có nhiều tham số lambda, đôi khi cần nội tuyến chỉ một số trong số chúng. Để làm điều này, noinline được sử dụng — nó ngăn nội tuyến một tham số lambda cụ thể, để nó như một đối tượng Function bình thường.

Công cụ sửa đổi crossinline giải quyết vấn đề ngược lại: lambda được nội tuyến, nhưng nonlocal return bị cấm. Điều này cần thiết khi lambda được sử dụng bên trong một lambda khác hoặc trong ngữ cảnh mà return không được phép (ví dụ: được chuyển đến Runnable).

kotlin
inline fun processWithCallback(
    data: String,
    crossinline onSuccess: (String) -> Unit,
    noinline onError: (Exception) -> Unit
) {
    try {
        val result = process(data)
        onSuccess(result)
    } catch (e: Exception) {
        onError(e)
    }
}

// noinline: onError có thể được lưu trong biến hoặc chuyển đi nơi khác
val errorHandler = { e: Exception -> log(e.message) }
processWithCallback("input", { println(it) }, errorHandler)

Trong ví dụ, onSuccess được đánh dấu là crossinline — nó sẽ được nội tuyến, nhưng không thể sử dụng return bên trong nó. onError được đánh dấu là noinline — nó không được nội tuyến, do đó nó có thể được truyền như một đối tượng, được lưu trong trường lớp hoặc được sử dụng như một listener.

Hạn chế của hàm inline và khuyến nghị

Hàm inline có các hạn chế. Hàm inline đệ quy bị cấm — trình biên dịch sẽ trả về lỗi. Hàm inline không thể có khả năng hiển thị private hoặc internal nếu được khai báo trong một mô-đun khác, nhưng đây là giới hạn về khả năng hiển thị, không liên quan đến cơ chế nội tuyến.

Kích thước bytecode tăng lên sau mỗi lần gọi hàm inline, vì thân hàm được sao chép. Theo Kotlin Coding Conventions (JetBrains, 2025), khuyến nghị chỉ sử dụng inline cho các hàm có kích thước lên đến 10–15 dòng. Đối với các hàm lớn, lợi ích từ việc nội tuyến lambda có thể bị triệt tiêu do kích thước APK tăng (quan trọng trên Android vì giới hạn 64K phương thức).

kotlin
// Phương pháp khuyến nghị
inline fun withLock(lock: Lock, action: () -> T): T {
    lock.lock()
    try {
        return action()
    } finally {
        lock.unlock()
    }
}

// Không khuyến nghị cho hàm lớn
inline fun largeComputation(...) { // xấu — thân >50 dòng
    // hơn 50 dòng — nên tách thành hàm thông thường
}

Các hàm inline công khai trong thư viện đòi hỏi thận trọng: nếu thân của một hàm inline thay đổi, tất cả khách hàng phải biên dịch lại. JetBrains khuyến nghị sử dụng @PublishedApi internal cho các thành viên được gọi từ hàm inline để duy trì tính tương thích trong một mô-đun.

Câu hỏi thường gặp

Có thể tạo hàm mở rộng inline không?

, hàm mở rộng inline hoạt động không có hạn chế. Ví dụ: inline fun String.transform(block: (Char) -> Char): String. Mở rộng không ảnh hưởng đến khả năng nội tuyến — trình biên dịch xử lý nó giống như một hàm inline thông thường.

Khi nào inline KHÔNG mang lại lợi ích?

Nếu hàm không chấp nhận tham số lambda — inline không mang lại lợi ích. Trình biên dịch Kotlin đưa ra cảnh báo: “Expected performance impact from inlining is insignificant. Inlining works best for functions with parameters of functional types.” Ngoài ra, inline có hại cho các hàm lớn do sự tăng trưởng bytecode.

Inline khác với @JvmInline (value class) như thế nào?

inline — một công cụ sửa đổi hàm để nội tuyến thân hàm tại vị trí gọi. @JvmInline (value class) — một cơ chế cho các lớp bao bọc được thay thế bằng giá trị của chúng tại thời điểm biên dịch. Các khái niệm khác nhau: inline tối ưu hóa lời gọi, value class tối ưu hóa biểu diễn dữ liệu.

Có thể sử dụng inline với hàm suspend không?

Không, hàm suspend không thể inline vì chúng được biên dịch thành máy trạng thái với Continuation. Tuy nhiên, một hàm inline có thể chấp nhận lambda suspend như một tham số với crossinline. Điều này thường được sử dụng trong coroutines: inline fun launch(block: suspend CoroutineScope.() -> Unit).

Inline có ảnh hưởng đến việc gỡ lỗi không?

, hàm inline làm phức tạp việc gỡ lỗi vì thân hàm không được gọi mà được nội tuyến tại vị trí gọi. Dấu vết ngăn xếp trở nên dài hơn, các điểm dừng hoạt động nhưng có thể hiển thị các vị trí không mong đợi. JetBrains khuyến nghị gỡ lỗi mà không có inline và chỉ kích hoạt nó trong các bản dựng release.

Tổng kết

  • Hàm inline — nội tuyến thân hàm tại vị trí gọi, loại bỏ overhead lambda
  • Nonlocal return — return từ lambda kết thúc hàm bên ngoài (chỉ khả thi với inline)
  • noinline — ngăn nội tuyến một tham số lambda cụ thể
  • crossinline — cho phép nội tuyến nhưng cấm nonlocal return
  • Hàm đệ quy không thể inline — trình biên dịch trả về lỗi
  • Kích thước bytecode tăng — sử dụng inline cho các hàm đến 10–15 dòng
  • Sử dụng tối ưu — hàm bậc cao với tham số lambda, khối đồng bộ hóa, hàm phạm vi (let, apply, also, run)

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm