Kotlin là ngôn ngữ lập trình mới nhất trên nền tảng JVM, được phát triển bởi JetBrains. Google đã chính thức công nhận Kotlin là ngôn ngữ phát triển Android bên cạnh Java. Theo đánh giá của các lập trình viên, Kotlin đã giải quyết nhiều hạn chế còn tồn tại trong Java. Tôi đã biên soạn nhiều hướng dẫn về Kotlin và dưới đây là một số câu hỏi phỏng vấn Kotlin phổ biến.

Câu hỏi phỏng vấn Kotlin
Dưới đây là các câu hỏi phỏng vấn Kotlin kèm câu trả lời chi tiết. Những câu hỏi này sẽ hữu ích cho cả người mới bắt đầu lẫn lập trình viên có kinh nghiệm. Ngoài ra còn có các câu hỏi lập trình để giúp bạn ôn luyện kỹ năng coding.
- Nền tảng mục tiêu của Kotlin là gì? Làm thế nào để Kotlin và Java có thể tương tác với nhau?
Máy ảo Java (Java Virtual Machine – JVM) là nền tảng mục tiêu của Kotlin. Kotlin tương thích 100% với Java vì cả hai, sau khi biên dịch, đều tạo ra bytecode. Do đó, mã Kotlin có thể được gọi từ Java và ngược lại.
- Làm thế nào để khai báo biến trong Kotlin? Sự khác biệt với cách khai báo trong Java là gì?
Có hai điểm khác biệt chính giữa Java và Kotlin trong khai báo biến:
- Cách khai báo:Trong Java:
String s = "Java String"; int x = 10;Trong Kotlin:
val s: String = "Hi" var x = 5Trong Kotlin, khai báo bắt đầu với val hoặc var, theo sau là kiểu dữ liệu tùy chọn. Kotlin có thể tự động xác định kiểu dữ liệu bằng cơ chế suy luận kiểu (type inference).
- Giá trị mặc định:Trong Java:
String s;Câu lệnh trên là không hợp lệ trong Kotlin:
val s: String
- Sự khác nhau giữa khai báo
valvàvarlà gì? Làm thế nào để chuyển một chuỗi thành số nguyên?
Biến val không thể bị thay đổi. Chúng giống như các biến có từ khóa final trong Java. Biến var có thể được gán lại giá trị, nhưng giá trị được gán lại phải có cùng kiểu dữ liệu.
fun main(args: Array<String>) {
val s: String = "Hi"
var x = 5
x = "6".toInt()
}
Chúng ta sử dụng phương thức toInt() để chuyển đổi String thành Int.
- Null Safety và Nullable Types trong Kotlin là gì? Toán tử Elvis là gì?
Kotlin đặc biệt chú trọng vào tính an toàn với giá trị null (null safety), đây là một cách tiếp cận nhằm ngăn chặn lỗi Null Pointer Exception bằng cách sử dụng các kiểu dữ liệu có thể null (nullable types) như String?, Int?, Float?, v.v. Các kiểu này hoạt động như một lớp wrapper và có thể chứa giá trị null.
Một giá trị nullable không thể cộng trực tiếp với một giá trị nullable khác hoặc với giá trị thuộc kiểu dữ liệu cơ bản. Để lấy giá trị kiểu cơ bản, chúng ta cần sử dụng safe call để giải nén kiểu nullable. Nếu khi mở ra mà giá trị là null, chúng ta có thể bỏ qua hoặc dùng giá trị mặc định.
Toán tử Elvis (Elvis Operator) được dùng để mở giá trị nullable một cách an toàn. Nó được biểu diễn bằng ký hiệu ?: đặt sau kiểu nullable. Giá trị bên phải toán tử sẽ được dùng nếu giá trị nullable là null.
var str: String? = "JournalDev.com"
var newStr = str ?: "Default Value"
str = null
newStr = str ?: "Default Value"

constlà gì? Khác biệt như thế nào vớival?
Theo cấu hình mặc định, các thuộc tính khai báo bằng val sẽ được gán giá trị tại thời gian chạy. Việc thêm từ khóa const vào một val sẽ biến nó thành hằng số tại thời gian biên dịch (compile-time constant).
Một const không thể được sử dụng với var hoặc đứng một mình, và cũng không áp dụng cho biến cục bộ (local variable).

- Kotlin có cho phép sử dụng các kiểu nguyên thủy như int, float, double không?
Ở cấp độ ngôn ngữ, Kotlin không cho phép khai báo trực tiếp các kiểu này. Tuy nhiên, khi biên dịch sang bytecode của JVM thì vẫn sử dụng các kiểu nguyên thủy tương ứng.
- Điểm bắt đầu của chương trình Kotlin là gì?
Hàm main là điểm bắt đầu của mọi chương trình Kotlin. Trong Kotlin, chúng ta không cần đặt hàm main trong một class vì khi biên dịch, JVM sẽ tự động đóng gói nó vào một class. Các chuỗi trong Array<String> là đối số dòng lệnh.
!!khác với?.như thế nào trong việc giải nén giá trị nullable? Có cách nào khác để giải nén giá trị nullable một cách an toàn không?
!! được sử dụng để ép buộc giải nén kiểu dữ liệu nullable để lấy giá trị. Nếu giá trị trả về là null, thao tác này sẽ dẫn đến lỗi dừng chương trình (runtime crash). Vì vậy, toán tử !! chỉ nên được sử dụng khi bạn hoàn toàn chắc chắn rằng giá trị sẽ không bao giờ là null. Nếu không, bạn sẽ gặp lỗi null pointer exception.
Ngược lại, ?. là một toán tử gọi an toàn (safe call operator). Chúng ta có thể sử dụng biểu thức lambda let trên giá trị nullable để giải nén an toàn, như trong ví dụ dưới đây.

Ở đây, biểu thức let thực hiện một lời gọi an toàn để giải nén kiểu dữ liệu có thể null.
- Hàm được khai báo như thế nào? Tại sao trong Kotlin gọi là hàm top-level?
fun sumOf(a: Int, b: Int): Int {
return a + b
}
Kiểu trả về của một hàm được khai báo sau dấu :. Trong Kotlin, các hàm có thể được khai báo ở gốc của file Kotlin (root level).
- Khác biệt giữa toán tử
==và===trong Kotlin là gì?
==được dùng để so sánh giá trị có bằng nhau hay không.===được dùng để kiểm tra xem tham chiếu có trỏ đến cùng một đối tượng hay không.
- Liệt kê các chỉ định truy cập (visibility modifiers) có sẵn trong Kotlin. Chỉ định truy cập mặc định là gì?
publicinternalprotectedprivate
Mặc định là public.
- Đoạn mã kế thừa sau có biên dịch được không?
class A {
}
class B : A() {
}
KHÔNG. Mặc định các class trong Kotlin là final. Phải thêm từ khóa open để cho phép kế thừa:
open class A {
}
class B : A() {
}
- Có bao nhiêu loại constructor trong Kotlin? Khác biệt ra sao? Định nghĩa như thế nào?
Có hai loại constructor:
- Primary Constructor: Khai báo trực tiếp trên phần đầu class, không chứa logic. Mỗi class chỉ có một.
- Secondary Constructor: Khai báo trong thân class, có thể chứa logic và phải gọi lại primary constructor (nếu có). Có thể có nhiều secondary constructors.
class User(name: String, isAdmin: Boolean) {
constructor(name: String, isAdmin: Boolean, age: Int) : this(name, isAdmin) {
this.age = age
}
}
initblock trong Kotlin là gì?
init là khối khởi tạo, được thực thi ngay sau khi primary constructor được gọi. Nếu gọi secondary constructor, init vẫn được thực thi đầu tiên do nó là một phần của chuỗi khởi tạo.
- String interpolation hoạt động như thế nào trong Kotlin? Ví dụ?
String interpolation cho phép nhúng biến vào chuỗi bằng ký hiệu $. Nếu là biểu thức, ta dùng ${}.
val name = "Journaldev.com"
val desc = "$name now has Kotlin Interview Questions too. ${name.length}"
Sử dụng dấu {} chúng ta cũng có thể tính toán một biểu thức.
- Kiểu dữ liệu của đối số trong constructor là gì?
Mặc định là val nếu không khai báo rõ ràng.
- Kotlin có từ khóa
newkhông? Làm sao để khởi tạo đối tượng?
KHÔNG. Không giống như Java, trong Kotlin, new không phải là một từ khóa. Chúng ta có thể khởi tạo một lớp theo cách sau:
class A
var a = A()
val new = A()
- Tương đương với biểu thức
switchtrong Kotlin là gì? Khác biệt ra sao?
when trong Kotlin tương đương với switch trong Java. Câu lệnh mặc định trong when được biểu diễn bằng từ khóa else.
var num = 10
when (num) {
0..4 -> print("value is 0")
5 -> print("value is 5")
else -> {
print("value is in neither of the above.")
}
}
Không cần dùng break.
- Data class trong Kotlin là gì? Lợi ích? Định nghĩa ra sao?
Trong Java, để tạo một lớp lưu trữ dữ liệu, bạn cần khai báo các biến, viết các phương thức getter và setter, ghi đè các hàm toString(), hash() và copy().
Trong Kotlin, bạn chỉ cần thêm từ khóa data vào khai báo lớp và tất cả những thành phần trên sẽ được tự động tạo ra bên dưới (under the hood).
data class Book(var name: String, var authorName: String)
fun main(args: Array<String>) {
val book = Book("Kotlin Tutorials", "Anupam")
}
Do đó, data class giúp chúng ta tiết kiệm rất nhiều mã nguồn. Nó còn tạo ra các hàm thành phần (component functions) như component1()… componentN() cho từng biến trong lớp. Ảnh 4
- Destructuring Declarations trong Kotlin là gì? Ví dụ?
Destructuring Declarations dùng để gán nhiều giá trị cho các biến từ dữ liệu được lưu trong đối tượng hoặc mảng.
Ảnh 5
Bên trong dấu ngoặc đơn, chúng ta khai báo các biến. Ở cấp độ bên trong (under the hood), Destructuring Declarations sẽ tạo ra các hàm component tương ứng cho từng biến của lớp.
- Khác biệt giữa hàm
inlinevàinfixlà gì? Ví dụ?
Inline functions được sử dụng để giảm chi phí bộ nhớ bằng cách ngăn việc cấp phát đối tượng cho các hàm ẩn danh hoặc biểu thức lambda được gọi. Thay vì vậy, phần thân của hàm sẽ được chèn trực tiếp vào hàm gọi nó tại thời điểm chạy. Điều này có thể làm tăng kích thước bytecode một chút nhưng lại tiết kiệm đáng kể bộ nhớ.
Ảnh 6
Ngược lại, infix functions được dùng để gọi hàm mà không cần dấu ngoặc tròn hoặc ngoặc vuông. Cách viết này giúp mã nguồn trông giống như ngôn ngữ tự nhiên hơn.
Ảnh 7
- Khác biệt giữa
lazyvàlateinitlà gì?
Cả hai đều được sử dụng để trì hoãn việc khởi tạo thuộc tính trong Kotlin.
lateinit là một modifier được dùng với var và được sử dụng để gán giá trị cho var tại một thời điểm sau đó.
lazy là một phương thức hay đúng hơn là một biểu thức lambda, chỉ được dùng với val. val sẽ được tạo ra tại thời điểm chạy khi nó được yêu cầu.
val x: Int by lazy { 10 }
lateinit var y: String
- Làm sao tạo class Singleton trong Kotlin?
Dùng từ khóa object để khai báo:
object MySingletonClass
Một object trong Kotlin không thể có constructor. Tuy nhiên, chúng ta vẫn có thể sử dụng khối init bên trong nó.
- Kotlin có từ khóa
statickhông? Tạo phương thức tĩnh như thế nào?
KHÔNG. Kotlin không có từ khóa static.
Để tạo phương thức tĩnh trong lớp, chúng ta sử dụng companion object.
Mã Java tương ứng:
class A {
public static int returnMe() { return 5; }
}
Mã Kotlin tương đương sẽ như sau:
class A {
companion object {
fun a() : Int = 5
}
}
Để gọi phương thức này, ta chỉ cần viết: A.a().
- Kiểu dữ liệu của mảng sau là gì?
val arr = arrayOf(1, 2, 3);
Trả lời: Kiểu dữ liệu là Array<Int>.
Như vậy là chúng ta đã hoàn tất phần câu hỏi và trả lời phỏng vấn về Kotlin.