Please enable JavaScript.
Coggle requires JavaScript to display documents.
nấv ghỏhP, println("Logging user: $it") - Coggle Diagram
nấv ghỏhP
Kotlin
Default and Named arguments là gì? Ưu điểm?
Named arguments Là khi bạn chỉ định rõ tên tham số lúc gọi hàm, giúp rõ nghĩa và tránh lỗi truyền nhầm vị trí.
fun createUser(name: String, age: Int, country: String) { println("Name: $name, Age: $age, Country: $country") }
// Gọi bình thường
createUser("Lan", 20, "Vietnam")
// Dùng named arguments
createUser( name = "Lan", country = "Vietnam", age = 20 )
Dễ đọc, dễ hiểu
Không quan trọng thứ tự nếu dùng toàn bộ named arguments
Tránh lỗi khi có nhiều tham số cùng kiểu (Int, String, v.v.)
Default arguments là khi bạn định nghĩa giá trị mặc định cho tham số trong khai báo hàm.
fun greet(name: String, greeting: String = "Hello") { println("$greeting, $name!") }
greet("An") // Output: Hello, An!
greet("An", "Chào") // Output: Chào, An!
Không cần overload nhiều hàm
Tránh viết lại logic nhiều lần
Dễ mở rộng API mà không phá vỡ mã cũ
Sealed class khác gì so với abstract class?
Sealed class
Hạn chế tập con (subclasses) trong phạm vi nhất định
✅ Chỉ được khai báo cùng file
Dùng với when exhaustive:✅ Có (compiler bắt buộc xử lý hết)
Có thể chứa code: có thể chứa hàm và các thuộc tính
Dùng tốt nhất khi nào? ✅ Model các trạng thái hữu hạn
sealed class Result {
object Loading : Result()
data class Success(val data: String) : Result()
data class Error(val throwable: Throwable) : Result()
}
// Dùng với when expression
fun handleResult(result: Result) {
when (result) {
is Result.Loading -> println("Loading...")
is Result.Success -> println("Success: ${result.data}")
is Result.Error -> println("Error: ${result.throwable}"
) // ✅ compiler sẽ cảnh báo nếu thiếu case } }
Abstract class
Xây dựng lớp nền (base class) có thể mở rộng
❌ Không kiểm soát – có thể ở file khác
Dùng với when exhaustive:❌ Không
Có thể chứa code: có
Dùng tốt nhất khi nào? Tạo base class cho nhiều class có hành vi chung
abstract class Animal(val name: String) {
abstract fun speak()
}
class Dog(name: String) : Animal(name) {
override fun speak() = println("$name says Woof")
}
Sealed class phù hợp khi mình muốn mô hình hóa một tập hợp cố định và được kiểm soát các subclass – ví dụ như các trạng thái mạng (Loading, Success, Error). Trong khi đó, abstract class dùng để làm base class có thể mở rộng một cách tự do hơn. Điểm mạnh của sealed class là dùng rất tốt với when vì compiler có thể kiểm tra tính đầy đủ.
difference between == and ===
== So sánh bằng về mặt giá trị — Structural Equality
So sánh nội dung/giá trị bên trong của 2 đối tượng có bằng nhau hay không.
Thực chất nó gọi phương thức .equals() của đối tượng.
val a = "xin chào"
val b = "xin chào"
println(a == b)
// true vì nội dung giống nhau
=== So sánh tham chiếu — Referential Equality
So sánh 2 biến có cùng tham chiếu tới một đối tượng trong bộ nhớ hay không.
Kiểm tra xem 2 biến có phải trỏ đến đúng 1 vùng nhớ không.
val a = "xin chào"
val b = "xin chào"
val c = a
println(a === b) // false vì tuy nội dung giống nhưng là 2 đối tượng khác nhau
println(a === c) // true vì cả 2 cùng tham chiếu tới 1 đối tượng
Lambda expression trong Kotlin
là một khối code có thể được truyền như một đối số cho một hàm hoặc được gán vào một biến
được dùng rộng rãi trong thực tế, nhất là trong Android (VD: xử lý sự kiện, callback, filter, map, v.v.).
ví dụ
// Higher-order function
fun doOperation(a: Int, b: Int, op: (Int, Int) -> Int): Int {
return op(a, b) }
// Gọi function val result = doOperation(5, 3) { x, y -> x + y } // 8
Reified type parameter trong inline function là gì?
Coroutines là gì? So sánh với Thread truyền thống?
Coroutines là các hàm có thể tạm dừng (suspend) và tiếp tục lại tại một điểm cụ thể mà không chặn luồng chính (main thread).
Thành phần
async Trả về Deferred<T>, dùng với await() để lấy kết quả
val result = async { ... }.await()
val a = async { getA() }
val b = async { getB() }
val result = a.await() + b.await()
launch Khởi chạy coroutine không trả kết quả viewModelScope.launch { ... }
delay(ms) Tạm dừng coroutine không chặn luồng
suspend Đánh dấu hàm có thể bị tạm dừng
suspend fun getUser()
CoroutineScope Quản lý phạm vi sống của coroutine viewModelScope, lifecycleScope
GlobalScope App-wide Không nên dùng trừ khi có lý do rõ ràng
lifecycleScope Activity / Fragment Tự hủy theo vòng đời UI
viewModelScope ViewModel Tự hủy khi ViewModel bị clear
Dispatchers Quy định coroutine chạy trên thread nào (Main, IO, Default)
Dispatchers.IO Gọi API, đọc/ghi file, DB
Dispatchers.Default Tính toán nặng
Dispatchers.Main UI thread (update UI, LiveData)
Dispatchers.Unconfined Không ràng buộc thread, nâng cao
Một số kỹ thuật nâng cao
withContext(Dispatchers.IO): chuyển luồng xử lý
supervisorScope: giúp các coroutine con không ảnh hưởng nhau khi 1 cái lỗi
CoroutineExceptionHandler: bắt lỗi toàn cục
higher-order function
là Hàm nhận hoặc trả về hàm khác
ứng dụng Callback, click listeners, network response handler
điểm khác với interface là Functional style gọn gàng, ít boilerplate hơn
ví dụ
// Higher-order function
fun doOperation(a: Int, b: Int, op: (Int, Int) -> Int): Int {
return op(a, b) }
// Gọi function val result = doOperation(5, 3) { x, y -> x + y } // 8
suspend function là gì? CoroutineScope, Dispatchers dùng để làm gì?
Suspend function là gì? Là hàm có thể bị tạm dừng (suspend) trong quá trình thực thi mà không làm block (khóa) thread đang chạy.
Chỉ được gọi bên trong coroutine hoặc từ hàm suspend khác.
Giúp bạn viết code bất đồng bộ theo kiểu tuần tự, dễ đọc.
CoroutineScope là gì? Là phạm vi (scope) để khởi chạy và quản lý vòng đời của các coroutine.
Giúp bạn kiểm soát khi nào coroutine bị hủy, tránh rò rỉ bộ nhớ (memory leak).
Ví dụ: trong Android có các scope chuẩn như: viewModelScope (thuộc ViewModel)
lifecycleScope (thuộc Activity/Fragment)
Dispatcher quyết định coroutine sẽ chạy trên luồng (thread) nào.
Dispatchers.Main Chạy trên UI thread, cập nhật giao diện
Dispatchers.IO Chạy các tác vụ nặng về I/O như đọc/ghi file, mạng, database
Dispatchers.Default Chạy các tác vụ tính toán CPU nặng
Dispatchers.Unconfined Chạy trên thread gọi coroutine, nâng cao
CoroutineScope(Dispatchers.IO).launch {
val data = fetchFromNetwork()
withContext(Dispatchers.Main) {
updateUI(data) } }
Companion object là gì? Dùng để thay thế static ra sao?
Trong Kotlin,
companion object
được dùng để thay thế từ khóa static trong Java. Vì Kotlin không có static như Java, nên companion object là cách để khai báo các hàm hoặc biến dùng chung mà không cần tạo instance của class.
class MyClass {
companion object {
val TAG = "MyClass"
fun log(message: String) {
println("$TAG: $message") } } }
Sử dụng:
MyClass.log("Xin chào") // Không cần tạo MyClass()
Note
Có thể đặt tên cho companion object
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
Sử dụng:
val obj = MyClass.create()
Có thể implement interface trong companion object
interface Factory<T> {
fun create(): T
}
class MyClass {
companion object : Factory<MyClass> {
override fun create(): MyClass = MyClass()
}
}
Kotlin không có static – Vì
Kotlin hướng đến lập trình hướng đối tượng "thuần"
static là thuộc tính cấp class (class-level) trong Java, không phù hợp với triết lý của Kotlin
companion object linh hoạt hơn, có thể implement interface, đặt tên, hoặc mở rộng bằng extension
Kotlin
val S ở top-level hoặc companion
fun greet() ở top-level hoặc companion
object Singleton
Java
static fun greet()
static final String S
static class Singleton
Flow vs Channel vs LiveData khác nhau như thế nào?
Extension function
(hàm mở rộng) trong Kotlin cho phép thêm hàm mới vào class đã tồn tại mà không cần kế thừa hoặc sửa đổi class đó.
fun ClassName.functionName(params): ReturnType { // logic mở rộng }
Difference between apply, let, also, with, run?
//also Thêm hành động phụ (log, debug)
val user2 = user1.also { println("Logging user: $it") }
// with: gọi nhiều hàm trên đối tượng, trả về kết quả lambda
val result = with(user1) {
println(name)
age + 10 }
// let: Biến đổi, xử lý nullable
val name: String? = "Kotlin"
// dùng let với nullable để tránh null pointer
val length = name?.let { println(it) // in ra "Kotlin" nếu name không null
it.length // trả về độ dài chuỗi
} ?: 0 // nếu name null thì length = 0
println(length) // 6
//run Nó kết hợp đặc điểm của apply (dùng this) và let (trả về kết quả của lambda).
val length2 = user1.run {
println(name) // dùng 'this.name' nhưng 'this' được ngầm hiểu nên có thể viết ngắn
age + 5 // kết quả cuối cùng của lambda, trả về và gán cho length2 }
// apply: cấu hình đối tượng, trả về chính đối tượng
val user1 = User("A", 20).apply { name = "John" age = 25 }
Null safety
được thiết kế ngay trong ngôn ngữ để giảm thiểu lỗi NullPointerException
var name: String = "Nam" // Không thể null
var nickname: String? = null // Có thể null (dấu
?
)
Toán tử ?. (Safe call)
Chỉ gọi thuộc tính/phương thức nếu biến khác null
val length = nickname?.length // Nếu nickname != null → lấy length, ngược lại → null
Toán tử ?: (Elvis operator)
val length = nickname?.length ?: 0
Dùng để cung cấp giá trị mặc định nếu biến là null
Toán tử !! (Non-null assertion)
Khẳng định rằng biến không null (nếu sai sẽ crash ngay)
val length = nickname!!.length // Nếu nickname == null → ném NPE
let với safe call
Thường dùng khi muốn xử lý logic trong khối if not null
nickname?.let { println("Nickname is $it") }
Smart cast sau khi kiểm tra != null
fun printLength(nick: String?) {
if (nick != null) {
println(nick.length) // Smart-cast thành String
}}
var và val khác nhau như thế nào?
var – Biến có thể thay đổi (mutable)
val – Biến không thể thay đổi (immutable)
Kotlin so với Java
Smart Cast
Kiểm tra với is (Instance Check)
Khi bạn sử dụng toán tử is để kiểm tra kiểu của đối tượng, Kotlin sẽ tự động ép kiểu nếu bạn kiểm tra và sử dụng đối tượng sau kiểm tra.
if (obj is String) {}
Kiểm tra với !is
Kotlin cũng hỗ trợ toán tử !is để kiểm tra đối tượng không phải là một kiểu cụ thể, và khi kiểm tra này, Kotlin cũng sẽ không yêu cầu ép kiểu.
if (obj !is Int) {}
Các tình huống khác có thể sử dụng Smart Cast
Null safety: Nếu một biến không phải là null, Kotlin sẽ tự động hiểu rằng bạn có thể sử dụng đối tượng đó mà không cần kiểm tra thêm.
Smart Cast với when: Kotlin sẽ tự động ép kiểu khi bạn sử dụng when với các kiểu dữ liệu khác nhau.
Immutable variables là điều kiện để smart cast hoạt động, và khi đối tượng có thể thay đổi, smart cast sẽ không được sử dụng.
Data class
data class Person(val name: String, val age: Int)
tự động cung cấp một số tính năng hữu ích mà bạn thường cần khi làm việc với dữ liệu
equals() và hashCode(): Phương thức này được sử dụng để so sánh các đối tượng dựa trên giá trị của các thuộc tính.
copy(): Phương thức này tạo ra một bản sao của đối tượng, cho phép bạn thay đổi một hoặc một số thuộc tính mà không làm thay đổi bản gốc.
val user3 = user1.copy(name = "Minh")
println(user3) // User(id=1, name=Minh)
toString(): Phương thức này trả về một chuỗi mô tả đối tượng với các giá trị của các thuộc tính.
componentN(): Phương thức này cho phép bạn truy cập từng phần tử (thuộc tính) của đối tượng như các giá trị tuple.
val person = Person("Linh", 22)
println(person.component1()) // "Linh"
println(person.component2()) // 22
Extension Function: cho phép bạn thêm các phương thức mới vào một lớp đã tồn tại mà không cần phải kế thừa lớp đó hay thay đổi mã nguồn của lớp
Ví dụ 1 cú pháp: fun String.reverseString(): String {}
Trong Java, không có tính năng extension function tương tự Kotlin. Tuy nhiên, bạn có thể đạt được hiệu quả tương tự bằng cách sử dụng phương thức tĩnh trong một lớp helper hoặc utility.
Giúp mã nguồn trở nên ngắn gọn và dễ đọc hơn.
Bạn có thể mở rộng lớp mà không cần sửa đổi mã gốc.
Tính linh hoạt cao khi làm việc với các thư viện bên ngoài.
Cách mở rộng lớp:
Kotlin: Có thể mở rộng mọi lớp (kể cả các lớp trong thư viện chuẩn) mà không cần kế thừa
Java: Sử dụng các phương thức tĩnh trong lớp Helper
Coroutine
Null Safety: Có kiểm tra null ngay từ khi biên dịch (?, !!)
Interop với Java
một tính năng cực mạnh, cho phép Kotlin và Java giao tiếp, gọi code qua lại một cách mượt mà
Annotation hỗ trợ Interop
JvmField
Truy cập trực tiếp field, không qua getter/setter
JvmOverloads
Tạo nhiều overload của hàm Kotlin có default parameter để Java dùng được
Throws
Đánh dấu hàm Kotlin có thể ném Exception để Java nhận biết
JvmStatic
Cho phép gọi hàm trong companion object như static từ Java
Cú pháp: Ngắn gọn, dễ đọc
Smart cast hoạt động như thế nào?
Kotlin sẽ tự động hiểu và ép kiểu biến khi compiler chắc chắn rằng không có khả năng kiểu bị thay đổi sau khi kiểm tra.
fun printLength(obj: Any) {
if (obj is String) {
// Kotlin tự động hiểu obj là String trong block này println(obj.length) // Không cần ép kiểu } }
Khi nào Smart Cast không hoạt động?
Biến có thể thay đổi (var) hoặc bị override trong hàm:
fun check(x: Any) { if (x is String) { x.length // ❌ Lỗi nếu x là var, vì Kotlin không chắc x có bị thay đổi không } }
Khi ép kiểu ngoài phạm vi if:
if (obj is String) { // OK } println(obj.length) // ❌ Không cast được ngoài phạm vi kiểm tra
Kotlin Collections có gì khác so với Java Collections?
Destructuring declarations là gì?
Bạn xử lý API bằng Retrofit và Coroutines như thế nào?
Cách viết ViewModel với Coroutine & LiveData?
Làm sao để tránh memory leak khi dùng Coroutine?
Cancel coroutine đúng lúc
Nếu bạn tạo coroutine bằng GlobalScope hoặc CoroutineScope(...), bạn phải tự cancel khi không cần nữa:
private val myScope = CoroutineScope(Dispatchers.Main + Job())
override fun onDestroy() { s
uper.onDestroy()
myScope.cancel() // Hủy toàn bộ coroutine trong scope }
Không nên dùng GlobalScope trong UI hoặc logic liên quan đến vòng đời!
Không giữ tham chiếu đến Context/UI bên trong Coroutine
Cách đúng:
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { lifecycleScope.launch {
delay(10000)
if (isActive) { textView.text = "Hello" } } } }
Sử dụng đúng Scope theo vòng đời
LifecycleOwner (Activity/Fragment) lifecycleScope
ViewModel viewModelScope
Service Tạo CoroutineScope riêng và hủy đúng lúc
Sử dụng isActive, ensureActive() để kiểm tra coroutine còn sống
coroutineScope { val data = fetchData()
if (isActive) { updateUI(data) } }
Không dùng suspend function nặng trên Dispatchers.Main
viewModelScope.launch(Dispatchers.IO) {
val data = repo.loadData()
withContext(Dispatchers.Main) {
showData(data) } }
Bạn đã dùng sealed class để xử lý API state như thế nào?
Inline function là gì? Khi nào dùng?
Git
Sự khác nhau giữa git pull và git fetch là gì?
Sự khác biệt giữa merge và rebase là gì? Khi nào dùng cái nào?
Câu lệnh để tạo một nhánh mới và chuyển qua nhánh đó là gì?
Cách giải quyết conflict khi merge hai nhánh trong Git?
Bạn thường làm gì khi có conflict? Bạn có dùng công cụ nào để hỗ trợ merge không?
Bạn có biết .gitignore không? Dùng để làm gì? Trong Android nên ignore những gì?
Bạn đã bao giờ dùng stash chưa? Dùng khi nào?
Cách rollback hoặc undo lại một commit bị sai?
Làm sao để đảm bảo code bạn push không làm hỏng branch chính?
Sự khác biệt giữa HEAD, origin, master, main là gì?
Câu lệnh để tạo và chuyển sang một nhánh mới?
EventBus
Room Database
Networking RESTful API
Shared Preferences
Push Notifications
Clear Architecture
Firebase
Kiến trúc MVVM,MVC,MVP
Image Loaders
Design Patterns
Coroutine & Flow
println("Logging user: $it")