Throw/Throws — nó là gì, toán tử ném ngoại lệ và cách hoạt động trong phát triển di động

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

Throw và Throws là cơ chế ném và khai báo ngoại lệ trong các ngôn ngữ lập trình. Toán tử throw làm gián đoạn thực thi bình thường của hàm và truyền đối tượng lỗi lên trên ngăn xếp cuộc gọi. Từ khóa throws trong chữ ký hàm cảnh báo bên gọi về khả năng xảy ra lỗi, làm cho mã có thể dự đoán được. Theo Tài liệu Kotlin (2026), throw trong Kotlin là một biểu thức (expression), không phải là câu lệnh, cho phép sử dụng nó bên trong khối when và toán tử Elvis.

Điểm chính

  • Throw — một toán tử làm gián đoạn thực thi hàm và truyền đối tượng lỗi lên trên ngăn xếp cuộc gọi
  • Throws — một từ khóa trong chữ ký hàm khai báo rằng hàm có thể ném một ngoại lệ
  • Trong Swift hàm được đánh dấu bằng throws, lời gọi yêu cầu try/try?/try!, và throw chấp nhận bất kỳ loại nào triển khai giao thức Error
  • Trong Kotlin throw là một biểu thức kiểu Nothing, throws không tồn tại (tất cả ngoại lệ đều là unchecked)
  • Lỗi tùy chỉnh được tạo qua enum (Swift) hoặc sealed class (Kotlin) để xử lý thuận tiện trong catch

Throw và Throws là gì?

Throw là một toán tử tạo ra một ngoại lệ tại điểm thực thi chương trình. Khi gặp throw, luồng thực thi hiện tại bị gián đoạn ngay lập tức và quyền điều khiển được chuyển đến trình xử lý catch gần nhất trong ngăn xếp cuộc gọi. Nếu không tìm thấy trình xử lý, ứng dụng sẽ bị sập. Throw trong phát triển di động được sử dụng để báo hiệu các lỗi không thể xử lý ở mức trừu tượng hiện tại — ví dụ, phản hồi máy chủ không hợp lệ, không có mạng hoặc đối số không chính xác.

Throws — một bộ sửa đổi trong chữ ký hàm (chủ yếu trong Swift) khai báo rằng hàm có thể ném một lỗi. Đây là một phần của cơ chế lỗi checked trong Swift: bên gọi bắt buộc phải xử lý lỗi thông qua do-catch, try?, try! hoặc đánh dấu hàm của mình là throws để truyền tiếp. Trong Kotlin và Java, throws cũng tồn tại, nhưng Kotlin coi nó là dư thừa — tất cả ngoại lệ trong Kotlin đều là unchecked, nghĩa là chúng có thể không được xử lý mà không có sự cưỡng chế cú pháp. Theo Tài liệu Apple Swift (2026), throws trong Swift là cách duy nhất để khai báo rõ ràng khả năng xảy ra lỗi trong kiểu hàm, làm cho các hợp đồng API trở nên minh bạch cho nhà phát triển.

Sự khác biệt giữa throw và throws là cơ bản: throw là một hành động (ném ngoại lệ tại thời gian chạy), throws là một khai báo (hợp đồng tại thời gian biên dịch). Một hàm không có throws không thể sử dụng throw — trình biên dịch Swift sẽ tạo ra lỗi. Một hàm có throws có thể không sử dụng throw — điều này được phép nhưng vô nghĩa. Sự phân tách này làm cho throw/throws trở thành một công cụ thiết kế API mạnh mẽ, nơi hợp đồng lỗi có thể nhìn thấy trong chữ ký hàm trước khi gọi.

Throw trong Swift

Trong Swift, toán tử throw chấp nhận bất kỳ loại nào triển khai giao thức Error. Thông thường nhất là một enum với các trường hợp cho các loại lỗi khác nhau. Swift không hỗ trợ ngoại lệ checked kiểu Java — thay vào đó, kiểu hàm được đánh dấu bằng throws và việc xử lý được ủy quyền cho bên gọi. Điều này làm cho throw trong Swift linh hoạt hơn nhưng cũng là trách nhiệm nhiều hơn cho nhà phát triển.

Giao thức Error và các loại lỗi

Bất kỳ loại nào tuân thủ giao thức Error đều có thể được ném qua throw. Nhà phát triển thường sử dụng enum với các trường hợp không có giá trị liên kết (cho lỗi đơn giản) hoặc có giá trị liên kết (để truyền ngữ cảnh). Swift không yêu cầu lỗi phải là enum — bạn có thể sử dụng struct hoặc class triển khai Error, nhưng enum được ưa tiên nhờ exhaustive switch ở phía trình xử lý. Trình biên dịch kiểm tra rằng tất cả các trường hợp đều được xử lý trong do-catch.

swift
enum AuthError: Error {
    case invalidCredentials
    case tokenExpired
    case accountLocked(remainingMinutes: Int)
}

func login(username: String, password: String) throws -> Session {
    guard isValid(username) else {
        throw AuthError.invalidCredentials
    }
    let response = try api.authenticate(username, password)
    if response.isLocked {
        throw AuthError.accountLocked(
            remainingMinutes: response.lockDuration
        )
    }
    return Session(token: response.token)
}

AuthError xác định ba kịch bản: thông tin xác thực không hợp lệ, token hết hạn và tài khoản bị khóa với giá trị liên kết remainingMinutes. Hàm login được khai báo là throws — trình biên dịch yêu cầu gọi nó qua try. Bên trong hàm, throw được sử dụng ở hai nơi: cho tên người dùng không hợp lệ và cho tài khoản bị khóa. Giá trị liên kết accountLocked cho phép truyền dữ liệu cụ thể cho người dùng — bao nhiêu phút để chờ trước khi mở khóa. Cách tiếp cận này loại bỏ nhu cầu về các điểm cuối API riêng để kiểm tra trạng thái khóa.

Throw trong Kotlin

Trong Kotlin, throw là một biểu thức kiểu Nothing, không phải là câu lệnh. Điều này có nghĩa là throw có thể được sử dụng ở vế phải của phép gán, bên trong biểu thức when và toán tử Elvis ?:. Kiểu Nothing là một kiểu con đặc biệt của tất cả các kiểu trong Kotlin, cho phép sử dụng throw ở những nơi cần một giá trị thuộc bất kỳ kiểu nào. Trình biên dịch hiểu rằng việc thực thi không tiếp tục sau throw và không yêu cầu một nhánh cho trường hợp này.

Kiểu Nothing và vai trò của nó trong thành phần

Nothing là một kiểu duy nhất trong Kotlin, là kiểu con của tất cả các kiểu có thể. Một hàm trả về Nothing (ví dụ, TODO()) không bao giờ hoàn thành bình thường — hoặc luôn ném một ngoại lệ hoặc đi vào một vòng lặp vô hạn. Điều này làm cho throw trở thành ứng cử viên tự nhiên để sử dụng ở những nơi cần một giá trị: toán tử Elvis, when không có else, khởi tạo biến. Nếu throw nằm trong một nhánh when, trình biên dịch hiểu rằng nhánh đó dẫn đến Nothing và không yêu cầu return hoặc else cho nhánh đó.

kotlin
data class Config(val apiUrl: String, val timeoutSec: Int)

class ConfigParser {
    fun parse(json: String): Config {
        val obj = JSONObject(json)
        val url = obj.optString("apiUrl")
            ?: throw IllegalArgumentException("apiUrl is required")
        val timeout = obj.optInt("timeoutSec", 30)
        return Config(url, timeout)
    }

    fun getErrorMessage(code: Int): String {
        return when (code) {
            404 -> "Not found"
            500 -> "Server error"
            else -> throw IllegalArgumentException("Unknown code: $code")
        }
    }
}

Trong ví dụ đầu tiên, throw được sử dụng trong toán tử Elvis ?:: nếu trường apiUrl không có trong JSON, biểu thức throw ngay lập tức làm gián đoạn thực thi và ném một IllegalArgumentException. Kiểu Nothing cho phép trình biên dịch suy ra kiểu của vế phải là String (Elvis mong đợi String, throw có kiểu Nothing, Nothing là kiểu con của String). Trong ví dụ thứ hai, throw bên trong một biểu thức when: nếu mã không khớp với bất kỳ mã nào đã biết, một ngoại lệ sẽ được ném. Trình biên dịch hiểu rằng mã sau throw không thể truy cập được, do đó kiểu trả về của hàm String không bị vi phạm.

Throws trong chữ ký hàm và rethrows

Trong Swift, throws được chỉ định sau danh sách tham số và trước mũi tên kiểu trả về. Một hàm có throws chỉ có thể gọi các hàm throws khác bên trong do-catch hoặc với try?. Nếu một hàm throws không xử lý lỗi, nó sẽ truyền lỗi cho bên gọi. Swift cũng hỗ trợ rethrows — một bộ sửa đổi cho các hàm bậc cao chấp nhận một closure throws và truyền lỗi của nó. rethrows có nghĩa là hàm chỉ ném lỗi nếu closure được truyền vào đã ném một lỗi — bản thân hàm không tạo ra lỗi.

swift
func mapValues<T>(
    _ array: [T],
    transform: (T) throws -> U
) rethrows -> [U] {
    var result = [U]()
    for element in array {
        result.append(try transform(element))
    }
    return result
}

// Sử dụng với closure throws
let parsed = try mapValues(jsonStrings) { str in
    let data = Data(str.utf8)
    return try JSONDecoder().decode(Item.self, from: data)
}

rethrows cho phép hàm mapValues trở nên linh hoạt: nó chấp nhận cả closure throws và closure thông thường. Nếu một closure throws được truyền vào, lời gọi mapValues yêu cầu try; nếu thông thường, try không cần thiết. Điều này làm cho rethrows trở nên lý tưởng cho các hàm bậc cao như map, filter, reduce trong thư viện chuẩn Swift. Khuyến nghị của Apple: sử dụng rethrows cho các API chấp nhận closure throws và nguồn lỗi duy nhất là closure đó. Nếu hàm có thể ném lỗi của riêng mình, hãy sử dụng throws.

Ngoại lệ checked và unchecked

Throws trong Swift gần với ngoại lệ checked (như trong Java) — các lỗi được ném được khai báo trong chữ ký. Kotlin và Dart sử dụng ngoại lệ unchecked — throws trong chữ ký không bắt buộc. Sự khác biệt là cơ bản: checked buộc nhà phát triển phải xử lý lỗi (an toàn hơn nhưng dài dòng hơn), unchecked mang lại sự tự do nhưng tăng nguy cơ quên xử lý lỗi. Swift chọn checked cho throws, Kotlin chọn unchecked cho tất cả ngoại lệ. Cả hai cách tiếp cận đều có ưu điểm: Swift đáng tin cậy hơn ở cấp độ ngôn ngữ, Kotlin nhỏ gọn hơn và thuận tiện trong chuỗi biến đổi hàm.

Các loại lỗi tùy chỉnh

Để xử lý lỗi có cấu trúc trong ứng dụng di động, nên tạo các loại lỗi tùy chỉnh thay vì sử dụng Exception hoặc Error cơ bản. Trong Swift, sử dụng enum với giao thức Error; trong Kotlin, sealed class kế thừa từ Throwable (hoặc Exception); trong Dart, class kế thừa từ Exception. Các loại tùy chỉnh cho phép nhóm các lỗi theo danh mục và truyền dữ liệu liên kết.

Ngôn ngữLoại lỗiĐặc điểm
Swiftenum: Error { ... }Giá trị liên kết, exhaustive switch trong catch
Kotlinsealed class : Throwable()Data class cho lỗi có trường, biểu thức when
Dartclass implements ExceptionTrường Message, mệnh đề on trong catch
Javaclass extends ExceptionChecked và unchecked, throws bắt buộc trong chữ ký

Khi thiết kế lỗi tùy chỉnh, hãy tuân theo quy tắc: một lỗi — một kịch bản. Đừng kết hợp các nguyên nhân khác nhau trong một loại với cờ String message — hãy tạo các trường hợp/lớp con riêng cho mỗi kịch bản. Điều này cho phép bên gọi xử lý từng trường hợp thông qua pattern matching (when/switch) thay vì so sánh chuỗi. Trong Swift, điều này cung cấp exhaustive checking — trình biên dịch sẽ cảnh báo nếu một trường hợp nào đó của enum NetworkError không được xử lý.

Ví dụ lỗi tùy chỉnh trong Kotlin

kotlin
sealed class NetworkError(val message: String) : Throwable(message) {
    data class Timeout(val durationMs: Long) :
        NetworkError("Request timed out after ${durationMs}ms")
    data class HttpError(val code: Int, val body: String?) :
        NetworkError("HTTP $code")
    data class NoConnection(val cause: IOException) :
        NetworkError("No internet connection")
}

Sealed class NetworkError kế thừa từ Throwable (kiểu ngoại lệ chuẩn trong Kotlin). Mỗi lớp con là một data class với các trường riêng: Timeout chứa thời gian chờ tính bằng mili giây, HttpError chứa mã và nội dung phản hồi, NoConnection chứa IOException gốc. Thiết kế này cho phép xử lý từng lỗi qua when với đối sánh đầy đủ (khi bạn thêm một lớp con mới, trình biên dịch sẽ buộc cập nhật tất cả các biểu thức when).

Try, try? và try! trong Swift

Swift cung cấp ba cách để gọi hàm throws, mỗi cách có hợp đồng bảo mật riêng. try là cách tiêu chuẩn: yêu cầu do-catch hoặc ở bên trong một hàm throws. try? chuyển đổi lỗi thành nil — kết quả trở thành tùy chọn, khi có lỗi trả về nil, kiểu thay đổi từ T thành T?. try! — thực thi bắt buộc không xử lý: nếu một lỗi được ném, ứng dụng sẽ sập. Chỉ sử dụng try! khi bạn hoàn toàn chắc chắn rằng lỗi không thể xảy ra (ví dụ, dữ liệu rõ ràng hợp lệ).

swift
let configPath = Bundle.main.path(forResource: "config", ofType: "json")!

// try? — kết quả tùy chọn
let data = try? Data(contentsOf: URL(fileURLWithPath: configPath))
let json = try? JSONSerialization.jsonObject(with: data ?? Data())

// try! — thành công được đảm bảo (chỉ khi chắc chắn)
let decoder = JSONDecoder()
let defaultConfig = try! decoder.decode(
    Config.self,
    from: Config.defaultJSON
)

// try — xử lý tiêu chuẩn
do {
    let user = try fetchUser()
    showUser(user)
} catch let error as NetworkError {
    showRetryAlert(error.message)
}

Trong ví dụ, try! được sử dụng cho JSON rõ ràng hợp lệ được nhúng trong gói ứng dụng — lỗi giải mã là không thể xảy ra với một bản phát hành chính xác. try? được sử dụng để đọc tệp cấu hình — nếu tệp bị thiếu hoặc hỏng, ứng dụng sử dụng giá trị mặc định thay vì sập. try trong do-catch được sử dụng cho các yêu cầu mạng nơi lỗi được mong đợi và cần phản hồi từ người dùng. Khuyến nghị: tránh try! trong mã sản xuất — chỉ sử dụng nó cho dữ liệu hằng số đã được xác minh tại thời điểm xây dựng.

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

Sự khác biệt giữa throw và throws trong Swift là gì?

Throw là một toán tử ném lỗi trong quá trình thực thi chương trình, làm gián đoạn luồng. Throws là một bộ sửa đổi chữ ký hàm khai báo rằng hàm có thể ném lỗi. Một hàm không có throws không thể sử dụng throw. Throws là hợp đồng tại thời gian biên dịch, throw là hành động tại thời gian chạy.

Tại sao Kotlin không có throws?

Kotlin tuân theo triết lý ngoại lệ unchecked: tất cả ngoại lệ có thể không được xử lý mà không có sự cưỡng chế cú pháp. Các nhà phát triển Kotlin cho rằng throws trong Java dẫn đến các khối try-catch quá mức và bỏ qua các ngoại lệ checked thông qua catch rỗng. Kiểu Nothing của Kotlin cho phép sử dụng throw như một biểu thức, thay thế throws bằng một cách tiếp cận linh hoạt hơn.

Khi nào nên sử dụng try! trong Swift?

try! chỉ chấp nhận được khi bạn hoàn toàn chắc chắn rằng lỗi không thể xảy ra: JSON rõ ràng hợp lệ từ bundle, dữ liệu hằng số, lược đồ URL chính xác. Trong mã sản xuất, try! là một ngoại lệ, không phải quy tắc. try? được ưa tiên cho các kịch bản tùy chọn với giá trị dự phòng, try với do-catch cho xử lý lỗi bắt buộc.

Rethrows trong Swift là gì?

Rethrows là một bộ sửa đổi cho các hàm chấp nhận closure throws. Một hàm có rethrows chỉ ném lỗi nếu closure được truyền vào đã ném một lỗi. Điều này cho phép các hàm bậc cao (map, filter) hoạt động với cả closure throws và không throws mà không bắt buộc try ở bên gọi.

Có thể ném lỗi bên trong khối catch không?

Có, bên trong catch bạn có thể sử dụng throw để truyền lỗi lên trên ngăn xếp, bọc nó trong một loại khác hoặc thêm ngữ cảnh. Điều này được gọi là error chaining hoặc rethrow. Trong Swift, chỉ cần một throw khác trong catch; trong Kotlin, throw bên trong khối catch. Khối finally được thực thi trước khi quyền điều khiển được chuyển tiếp.

Tổng kết

  • Throw — một toán tử ném ngoại lệ làm gián đoạn luồng hiện tại và truyền đối tượng lỗi lên trên ngăn xếp
  • Throws — một khai báo tại thời gian biên dịch trong chữ ký hàm về khả năng xảy ra lỗi, bắt buộc trong Swift, không có trong Kotlin
  • Trong Swift throw chấp nhận enum: Error, hàm được đánh dấu bằng throws, được gọi qua try / try? / try!
  • Trong Kotlin throw là một biểu thức kiểu Nothing, cho phép sử dụng trong when, toán tử Elvis và phép gán
  • Rethrows trong Swift cho phép các hàm bậc cao truyền lỗi chỉ từ các closure được truyền vào
  • Lỗi tùy chỉnh được tạo qua enum (Swift) hoặc sealed class (Kotlin) với các giá trị liên kết để có thông tin chi tiết
  • Thiết kế loại lỗi theo nguyên tắc "một trường hợp — một kịch bản" để xử lý thuận tiện qua pattern matching

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