Kotlin 开发注意事项(Android Java 开发者转型指南)

本文档总结了 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)

注意事项:

  • 主构造函数至少需要一个参数
  • 参数必须使用 valvar 声明
  • 数据类不能是抽象、开放、密封或内部的
  • 解构声明支持: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,优先使用 lifecycleScopeviewModelScope
  • 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)
        }
}

十八、最佳实践总结

  1. 空安全优先 :使用 ?.?:,避免 !!
  2. 不可变优先 :优先使用 val 而不是 var
  3. 数据类:用于存储数据的场景
  4. 扩展函数:为现有类添加功能
  5. 协程:替代回调进行异步编程
  6. 高阶函数:简化代码,提高复用性
  7. 命名参数:提高代码可读性
  8. 智能转换:减少显式类型转换
  9. when 表达式:替代复杂的 if-else
  10. Sequence:大数据集使用延迟计算

十九、常见陷阱

  1. 可空类型与平台类型:Java 返回的可空类型在 Kotlin 中是平台类型
  2. 智能转换失效:可变属性不能智能转换
  3. 协程泄漏:忘记取消协程导致内存泄漏
  4. 扩展函数静态解析:不能实现多态
  5. 数据类复制:浅拷贝,深拷贝需要手动实现
  6. Sequence 一次性使用:不能多次遍历
  7. 内联函数限制:不能使用内联函数的类型参数进行实例化

参考资料:

相关推荐
wuqingshun3141592 小时前
产生死锁的四个必要条件
java·jvm
青槿吖2 小时前
第二篇:Spring MVC进阶:注解、返回值与参数接收的花式玩法
java·开发语言·后端·mysql·spring·mvc·mybatis
共享家95272 小时前
Java入门(抽象类 与 接口)
java·开发语言
hanbr2 小时前
C++ string类模拟实现(完整版,含全运算符重载)
java·开发语言
Kapaseker2 小时前
你可能还不知道 Compose Pager 有多强大
android·kotlin
xUxIAOrUIII2 小时前
【Go每日面试题】内存管理
java·开发语言·golang
阿捏利2 小时前
vscode+jadx-mcp-server配置及使用
android·apk·逆向·mcp·jadx
森屿山茶2 小时前
hot100题解 —— 146.LRU缓存
java·开发语言
gameboy0312 小时前
SpringbootActuator未授权访问漏洞
java