找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1552

积分

0

好友

223

主题
发表于 13 小时前 | 查看: 2| 回复: 0

Kotlin语言简洁高效,但在实际开发中,因使用习惯或忽视细节而导致的潜在问题并不少见,某些问题甚至可能引发线上严重故障。本文基于实战经验,总结了Kotlin编码中需要重点规避的几个“坑”,帮助你写出更健壮的代码。

一、应极力避免或谨慎使用的写法

1. 避免使用!!非空断言

使用!!操作符会绕过Kotlin的空安全机制,是空指针异常的潜在根源。即便当前上下文判断了非空,后续代码维护也可能疏忽。建议从设计上避免可空对象(如提供默认值),或使用更安全的方式,例如?.let作用域函数进行安全调用。

2. 避免使用as T?进行不安全的类型转换

as T?会在强制转换的对象并非T类型或其子类型时抛出ClassCastException异常。建议使用更安全的转换函数,如as?,它会在转换失败时返回null,避免程序崩溃。

3. 避免使用String.toXX()进行数值转换

这里特指String类的toInt()toFloat()等直接转换方法。一个常见案例是:与后端接口约定某个字段为数字字符串,但若该字段意外传空,toInt()将抛出NumberFormatException。应优先使用Kotlin提供的安全转换函数,并妥善处理null值:

// 不安全
val num = str.toInt()
// 安全
val safeNum = str.toIntOrNull() ?: 0 // 提供默认值

4. 谨慎使用lateinit

Android开发等场景中,lateinit有时是必要的(如在Activity.onCreate中初始化视图变量)。关键在于必须确保在首次访问属性前已完成初始化。滥用lateinit将导致“未初始化属性访问”异常。

二、与Java互操作时需注意的空安全

Kotlin与Java互操作时,其空安全机制可能被破坏,因为Java类型系统不具备可空性标识。假设有如下Java代码:

public interface ITest {
    void test(int code, String msg); // msg 可能为null
}
public class TestCode {
    public String getData() {
        return data; // 可能返回null
    }
}

在Kotlin侧编写代码时,应充分考虑Java可能传入null值:

class TestImpl : ITest {
    // 将参数声明为可空类型,以安全处理Java调用
    override fun test(code: Int, msg: String?) {
        // 使用前进行空判断
    }
}

// 以可空类型接收Java方法的返回值
val data: String? = TestCode().getData()

在处理来自Java的代码调用或返回值时,主动使用可空类型(?)是防御性编程的关键。

三、异常捕获与处理

Kotlin编译器不强制要求捕获已检查异常,这容易导致开发者遗漏对异常的处理,尤其是在I/O等操作中。

fun readFileToString(file: File): String {
    val reader = FileReader(file) // 可能抛出IOException
    // ... 读取操作
    return content
}

上述代码编译正常,但未处理IOException,也未关闭资源,线上风险极高。对于可能抛出异常的操作,务必使用try-catch进行捕获,或明确将异常向上抛出。
特别注意:禁止在finally块中使用return语句,因为它会覆盖trycatch块中的返回值,导致逻辑错误。

四、集合操作与多线程安全

1. 警惕数组越界

直接通过索引访问集合元素前,务必判断集合大小,或使用Kotlin提供的安全访问函数:

val list = listOf("A", "B")
// 危险
println(list[2])
// 安全做法1:判断大小
if (list.size > 2) println(list[2])
// 安全做法2:使用安全函数
println(list.getOrNull(2))

2. 避免ConcurrentModificationException

当集合可能被多线程修改时,非原子化的“检查-再操作”极易引发ConcurrentModificationException

val sharedList = mutableListOf<Int>()
// 线程不安全的写法
thread {
    if (sharedList.size >= 10) {
        sharedList.clear()
    }
    sharedList.add(1)
}

正确的做法是对共享资源的访问进行同步。这涉及到对并发控制机制的理解和应用:

// 使用 synchronized 进行同步
thread {
    synchronized(sharedList) {
        if (sharedList.size >= 10) {
            sharedList.clear()
        }
        sharedList.add(1)
    }
}

五、构造函数中禁止调用可被重写的方法

在父类构造函数(包括init初始化块)中调用可被子类重写的方法,可能导致访问到子类未初始化的属性。

open class Parent {
    init {
        println(getName()) // 危险:调用可被重写的方法
    }
    open fun getName() = "Parent"
}

class Child : Parent() {
    private val childName = "Child"
    override fun getName() = childName // 此时childName可能尚未初始化
}

fun main() {
    Child() // 打印结果可能不是预期的“Child”,甚至是null
}

原因是父类构造函数的执行先于子类属性的初始化。因此,在设计类时,应避免在构造函数中调用非final的成员方法。




上一篇:资深程序员防错指南:6个提升代码质量与稳定性的关键实践
下一篇:LVS+Keepalived+Nginx高可用架构实战:揭秘大厂千万级并发负载均衡方案
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-24 17:18 , Processed in 0.267179 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表