本文档总结了 Android Java 开发者转型 Kotlin 开发时需要注意的关键事项,帮助你快速掌握 Kotlin 的核心特性并避免常见陷阱。
一、空安全(Null Safety)
1.1 可空类型与非空类型
Kotlin 的类型系统区分可空类型和非空类型,这是与 Java 最大的区别之一。
kotlin
// 非空类型,永远不能为 null
var name: String = "Kotlin"
// 可空类型,可以为 null
var nickname: String? = null
// 编译错误:不能将 null 赋值给非空类型
// name = null // Error
注意事项:
- 默认情况下,所有类型都是非空的
- 使用
?标记可空类型 - 编译器会在编译时检查空安全,避免 NPE
1.2 空安全操作符
安全调用操作符 ?.
kotlin
val length = nickname?.length // 如果 nickname 为 null,返回 null
Elvis 操作符 ?:
kotlin
val length = nickname?.length ?: 0 // 如果为 null,返回默认值 0
非空断言 !!
kotlin
val length = nickname!!.length // 如果为 null,抛出 NPE(谨慎使用)
安全转换 as?
kotlin
val result = obj as? String // 转换失败返回 null 而不是抛出异常
最佳实践:
- 优先使用
?.和?:处理可空类型 - 避免使用
!!,除非你确定值不为 null - 在 API 边界(如网络请求、数据库查询)谨慎处理可空类型
二、扩展函数(Extension Functions)
Kotlin 允许为现有类添加新函数,而无需继承或修改源码。
kotlin
// 为 String 添加扩展函数
fun String.isEmail(): Boolean {
return this.contains("@")
}
// 使用
val email = "test@example.com"
if (email.isEmail()) {
println("Valid email")
}
注意事项:
- 扩展函数是静态解析的,不是虚函数
- 扩展函数不能访问私有成员
- 如果扩展函数与成员函数同名,成员函数优先级更高
Android 开发常用扩展:
kotlin
// View 扩展
fun View.show() {
visibility = View.VISIBLE
}
fun View.hide() {
visibility = View.GONE
}
// Context 扩展
fun Context.toast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
// Activity 扩展
inline fun <reified T : Activity> Activity.startActivity() {
startActivity(Intent(this, T::class.java))
}
三、数据类(Data Classes)
用于存储数据的类,自动生成 equals()、hashCode()、toString()、copy() 等方法。
kotlin
data class User(
val id: Int,
val name: String,
val email: String? = null
)
// 使用
val user1 = User(1, "Alice")
val user2 = user1.copy(name = "Bob")
println(user1) // User(id=1, name=Alice, email=null)
注意事项:
- 主构造函数至少需要一个参数
- 参数必须使用
val或var声明 - 数据类不能是抽象、开放、密封或内部的
- 解构声明支持:
val (id, name) = user
与 Java Bean 的区别:
- Java Bean:需要手动编写 getter/setter、equals、hashCode
- Kotlin 数据类:一行代码自动生成所有方法
四、高阶函数与 Lambda 表达式
4.1 Lambda 表达式语法
kotlin
// 完整语法
val sum = { a: Int, b: Int -> a + b }
// 简化语法
val sum: (Int, Int) -> Int = { a, b -> a + b }
// 调用
sum(1, 2) // 返回 3
4.2 高阶函数
函数可以作为参数传递或返回值。
kotlin
// 函数类型作为参数
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
// 调用
val result = calculate(10, 5) { x, y -> x * y } // 返回 50
4.3 常用集合操作
kotlin
val numbers = listOf(1, 2, 3, 4, 5)
// map:转换
val doubled = numbers.map { it * 2 } // [2, 4, 6, 8, 10]
// filter:过滤
val evens = numbers.filter { it % 2 == 0 } // [2, 4]
// reduce:累积
val sum = numbers.reduce { acc, n -> acc + n } // 15
// fold:带初始值的累积
val product = numbers.fold(1) { acc, n -> acc * n } // 120
// flatMap:扁平化映射
val nested = listOf(listOf(1, 2), listOf(3, 4))
val flat = nested.flatMap { it } // [1, 2, 3, 4]
注意事项:
- Lambda 表达式的参数如果只有一个,可以使用
it关键字 - 高阶函数可能导致性能问题(内联函数可优化)
- 使用
inline关键字优化高阶函数性能
4.4 内联函数
kotlin
inline fun <T> measureTime(block: () -> T): T {
val start = System.currentTimeMillis()
val result = block()
val end = System.currentTimeMillis()
println("Execution time: ${end - start}ms")
return result
}
五、协程(Coroutines)
轻量级线程,用于异步编程,替代 Java 的 Thread 和回调。
5.1 基本使用
kotlin
import kotlinx.coroutines.*
// 启动协程
GlobalScope.launch {
delay(1000) // 非阻塞延迟
println("World!")
}
// 在 Activity/Fragment 中使用
class MainActivity : AppCompatActivity() {
private val scope = MainScope()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
scope.launch {
// 在主线程执行
val result = withContext(Dispatchers.IO) {
// 在 IO 线程执行网络请求
apiService.getData()
}
// 回到主线程更新 UI
updateUI(result)
}
}
override fun onDestroy() {
super.onDestroy()
scope.cancel() // 取消所有协程
}
}
5.2 协程作用域
kotlin
// GlobalScope:全局作用域,生命周期与应用相同(谨慎使用)
GlobalScope.launch { }
// MainScope:主线程作用域
val mainScope = MainScope()
// lifecycleScope:与生命周期绑定(Activity/Fragment)
lifecycleScope.launch { }
// viewModelScope:与 ViewModel 生命周期绑定
viewModelScope.launch { }
注意事项:
- 使用
suspend关键字标记挂起函数 - 避免使用
GlobalScope,优先使用lifecycleScope或viewModelScope - 在
Dispatchers.IO执行网络/数据库操作 - 在
Dispatchers.Main更新 UI
六、属性与字段
6.1 属性声明
kotlin
// 只读属性(val)
val name: String = "Kotlin"
// 可变属性(var)
var age: Int = 10
// 自定义 getter/setter
var email: String = ""
get() = field.toLowerCase()
set(value) {
field = value.trim()
}
6.2 幕后字段(Backing Field)
Kotlin 使用 field 关键字访问幕后字段,避免递归调用。
kotlin
var counter: Int = 0
set(value) {
if (value >= 0) {
field = value // 使用 field 而不是 counter
}
}
注意事项:
val只有 getter,var有 getter 和 setter- 避免在 getter/setter 中直接访问属性本身(会导致递归)
- 使用
field访问幕后字段
七、智能类型转换
Kotlin 编译器会自动进行类型转换,减少显式转换。
kotlin
fun process(value: Any) {
if (value is String) {
// 自动转换为 String 类型
println(value.length)
}
if (value !is Int) return
// 自动转换为 Int 类型
println(value + 1)
}
// when 表达式中的智能转换
when (value) {
is String -> println(value.length)
is Int -> println(value + 1)
is List<*> -> println(value.size)
}
注意事项:
- 编译器会检查条件,确保类型安全
- 不能对可变属性进行智能类型转换(可能被其他线程修改)
八、单表达式函数
函数体只有一个表达式时,可以简化写法。
kotlin
// 传统写法
fun add(a: Int, b: Int): Int {
return a + b
}
// 单表达式函数
fun add(a: Int, b: Int) = a + b
// 带类型的单表达式函数
fun add(a: Int, b: Int): Int = a + b
九、默认参数与命名参数
9.1 默认参数
kotlin
fun greet(name: String, message: String = "Hello") {
println("$message, $name!")
}
greet("Alice") // Hello, Alice!
greet("Bob", "Hi") // Hi, Bob!
9.2 命名参数
kotlin
fun createUser(
id: Int,
name: String,
email: String = "",
age: Int = 0
) { }
// 使用命名参数
createUser(
id = 1,
name = "Alice",
age = 25 // 跳过 email 参数
)
注意事项:
- 默认参数可以避免方法重载
- 命名参数提高代码可读性
- 与 Java 互操作时,需要使用
@JvmOverloads注解生成重载方法
kotlin
@JvmOverloads
fun greet(name: String, message: String = "Hello") { }
// Java 调用
// greet("Alice")
// greet("Alice", "Hi")
十、解构声明
将对象解构成多个变量。
kotlin
data class User(val id: Int, val name: String)
val user = User(1, "Alice")
val (id, name) = user // 解构声明
// 在 for 循环中使用
val map = mapOf("a" to 1, "b" to 2)
for ((key, value) in map) {
println("$key -> $value")
}
// 在 Lambda 中使用
map.mapValues { (key, value) -> "$key = $value" }
十一、when 表达式
增强版的 switch 语句,更强大更灵活。
kotlin
// 基本用法
when (x) {
1 -> println("One")
2, 3 -> println("Two or Three")
in 4..10 -> println("Four to Ten")
!in 11..20 -> println("Not in 11-20")
else -> println("Other")
}
// 表达式形式
val result = when (x) {
1 -> "One"
else -> "Other"
}
// 检查类型
when (value) {
is String -> println(value.length)
is Int -> println(value + 1)
else -> println("Unknown")
}
// 无参数形式
when {
x > 10 -> println("Greater than 10")
x > 5 -> println("Greater than 5")
else -> println("Other")
}
十二、字符串模板
在字符串中嵌入变量和表达式。
kotlin
val name = "Kotlin"
// 变量模板
val greeting = "Hello, $name!"
// 表达式模板
val message = "Length: ${name.length}"
// 原始字符串(多行字符串)
val text = """
First line
Second line
""".trimIndent()
十三、区间与序列
13.1 区间(Range)
kotlin
// 闭区间
for (i in 1..10) {
println(i) // 1 到 10
}
// 开区间
for (i in 1 until 10) {
println(i) // 1 到 9
}
// 降序
for (i in 10 downTo 1) {
println(i) // 10 到 1
}
// 步长
for (i in 1..10 step 2) {
println(i) // 1, 3, 5, 7, 9
}
// 判断是否在区间内
if (x in 1..10) {
println("x is between 1 and 10")
}
13.2 序列(Sequence)
延迟计算的集合操作,避免中间集合的创建。
kotlin
// List:立即计算,创建中间集合
val result = listOf(1, 2, 3, 4, 5)
.map { it * 2 }
.filter { it > 5 }
.take(2)
// Sequence:延迟计算,性能更好
val result = sequenceOf(1, 2, 3, 4, 5)
.map { it * 2 }
.filter { it > 5 }
.take(2)
.toList()
注意事项:
- 数据量大时,优先使用
Sequence List适合小数据集和需要多次遍历的场景Sequence是一次性使用的
十四、伴生对象(Companion Object)
Kotlin 中没有 static 关键字,使用伴生对象实现类似功能。
kotlin
class MyClass {
companion object {
const val MAX_COUNT = 100
fun create(): MyClass {
return MyClass()
}
}
}
// 调用
MyClass.MAX_COUNT
MyClass.create()
// 与 Java 互操作
@JvmStatic
fun create(): MyClass {
return MyClass()
}
@JvmField
val MAX_COUNT = 100
注意事项:
- 伴生对象是单例的
- 使用
@JvmStatic和@JvmField注解优化 Java 调用 const val用于编译时常量
十五、委托(Delegation)
15.1 类委托
kotlin
interface Base {
fun print()
}
class BaseImpl : Base {
override fun print() { println("Base") }
}
// 类委托:将 Base 接口的实现委托给 b
class Derived(b: Base) : Base by b
// 使用
val b = BaseImpl()
Derived(b).print() // 输出: Base
15.2 属性委托
kotlin
import kotlin.properties.Delegates
// 延迟初始化
val lazyValue: String by lazy {
println("Computed!")
"Hello"
}
// 可观察属性
var name: String by Delegates.observable("<initial>") { prop, old, new ->
println("$old -> $new")
}
// vetoable:条件检查
var age: Int by Delegates.vetoable(0) { prop, old, new ->
new >= 0 // 只有新值 >= 0 才赋值
}
// 属性存储在 Map 中
class User(map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
val user = User(mapOf("name" to "Alice", "age" to 25))
十六、与 Java 互操作
16.1 Kotlin 调用 Java
kotlin
// Java 类
public class JavaClass {
public String getName() { return "Java"; }
}
// Kotlin 调用
val javaObj = JavaClass()
val name = javaObj.name // getter 自动转换为属性
16.2 Java 调用 Kotlin
kotlin
// Kotlin 类
class KotlinClass {
var name: String = ""
fun greet() {
println("Hello, $name")
}
companion object {
@JvmStatic
fun staticMethod() {
println("Static method")
}
}
}
// Java 调用
KotlinClass kotlinObj = new KotlinClass();
kotlinObj.setName("Java");
kotlinObj.greet();
KotlinClass.staticMethod();
16.3 注意事项
- Kotlin 的
List默认是只读的,与 Java 的List互操作时需注意 - Kotlin 的空安全在与 Java 互操作时可能失效(平台类型)
- 使用
@JvmOverloads生成重载方法 - 使用
@JvmStatic生成静态方法 - 使用
@JvmField暴露字段
十七、常用 Android Kotlin 扩展库
17.1 Android KTX
kotlin
// SharedPreferences
sharedPreferences.edit {
putString("key", "value")
}
// View
view.doOnPreDraw {
// View 绘制前执行
}
// Activity
activity.viewBinding // ViewBinding 扩展
// LiveData
liveData.map { it.toUpperCase() }
17.2 Coroutines
kotlin
// lifecycleScope
lifecycleScope.launch {
val result = repository.getData()
updateUI(result)
}
// viewModelScope
viewModelScope.launch {
val result = repository.getData()
_data.value = result
}
17.3 Flow
kotlin
// 创建 Flow
fun getDataFlow(): Flow<Result> = flow {
emit(Result.Loading)
val data = repository.getData()
emit(Result.Success(data))
}
// 收集 Flow
lifecycleScope.launch {
viewModel.dataFlow
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.collect { result ->
handleResult(result)
}
}
十八、最佳实践总结
- 空安全优先 :使用
?.和?:,避免!! - 不可变优先 :优先使用
val而不是var - 数据类:用于存储数据的场景
- 扩展函数:为现有类添加功能
- 协程:替代回调进行异步编程
- 高阶函数:简化代码,提高复用性
- 命名参数:提高代码可读性
- 智能转换:减少显式类型转换
- when 表达式:替代复杂的 if-else
- Sequence:大数据集使用延迟计算
十九、常见陷阱
- 可空类型与平台类型:Java 返回的可空类型在 Kotlin 中是平台类型
- 智能转换失效:可变属性不能智能转换
- 协程泄漏:忘记取消协程导致内存泄漏
- 扩展函数静态解析:不能实现多态
- 数据类复制:浅拷贝,深拷贝需要手动实现
- Sequence 一次性使用:不能多次遍历
- 内联函数限制:不能使用内联函数的类型参数进行实例化
参考资料:
- Kotlin 官方文档:https://kotlinlang.org/docs/
- Android Kotlin 指南:https://developer.android.com/kotlin
- Kotlin 协程:https://kotlinlang.org/docs/coroutines-overview.html