Kotlin上手记:从实验室项目到面试题的实战突围
上周五晚上十点半,实验室的灯还亮着。导师刚开完组会,扔下一句“下个月要给合作企业交付Android原型,谁熟悉Kotlin?”,然后转身就走。我和同门面面相觑——我们组主攻分布式系统,平时写Java写得多,Android?那不是本科毕设才碰的东西吗?
但没办法,项目 deadline 压着,产品方(对,就是那个总在群里@全体成员、需求改得比天气还快的产品经理)已经催了三轮 UI 稿。更扎心的是,我最近在偷偷投简历,结果好几家大厂的 Android 岗位 JD 里都写着“熟练掌握 Kotlin 者优先”。行吧,学!
于是这周我一边啃官方文档,一边把实验室的旧 Java Android 项目迁成 Kotlin,顺便整理出这份“非典型”入门指南。不搞那种“Hello World”的教程套路,而是聚焦真实问题怎么解——毕竟咱们研二狗没那么多时间玩虚的。
为什么是 Kotlin?不是被逼的,是真香
其实早在去年双11期间,我就注意到组里另一个做移动端的同学在用 Kotlin 写 demo。当时我还嗤之以鼻:“又整新活?Java 不香吗?” 直到他甩给我一段代码:
data class User(val id: Int, val name: String, val email: String)
对比我之前在分布式用户服务里写的 Java Bean,光 getter/setter 就占了二十行。Kotlin 这一行直接搞定,还自带 equals()、hashCode()、toString() 和 copy() —— 当时我就愣住了。
后来查了下,Google 在 2017 年就把 Kotlin 定为 Android 官方开发语言。现在新项目几乎清一色 Kotlin,连老项目也在逐步迁移。面试题里也常问:“Kotlin 相比 Java 有哪些优势?” 别人答空安全、扩展函数,我直接掏出项目截图:“看,我们组这个模块迁移后代码量减少40%,Crash 率降了60%。”
上手第一关:空指针?不存在的
说到 Crash,最让我头疼的就是 NullPointerException。上周测试同学提了个 bug:“点击用户头像闪退”,Log 一看,果然是 user.avatarUrl 为 null,结果我直接 .length() —— 经典 NPE,当场社死。
换成 Kotlin 后,这套逻辑重写如下:
fun isValidAvatar(url: String?): Boolean {
return url?.isNotEmpty() == true
}
注意那个 ? 和 == true。Kotlin 的类型系统天然区分可空与非空类型。String? 表示可能为 null,而 String 永远不会是 null。你要是强行解引用(比如写 url.length),编译器直接报错,根本跑不到线上。
这招在实验室的分布式配置中心项目里特别管用。以前 Java 读取配置项经常忘判空,现在 Kotlin 强制你处理,连 Code Review 都省了。
扩展函数:给别人的类“加功能”
产品那边上周又提了个需求:所有手机号显示时中间四位要打码。以前在 Java 里要么继承 TextView,要么写个工具类,调用起来贼啰嗦:
String masked = PhoneUtils.mask(phone);
textView.setText(masked);
Kotlin 的扩展函数直接让 String 自带打码能力:
fun String.maskPhone(): String {
if (length != 11) return this
return "${substring(0, 3)}****${substring(7)}"
}
// 使用
textView.text = phone.maskPhone()
是不是清爽多了?这招我在实验室的监控日志模块也用了——给 Throwable 加个 toReadableString(),再也不用看满屏堆栈吓自己。
协程:告别回调地狱
说到异步,Java 时代写网络请求简直噩梦。Retrofit + RxJava 虽然能用,但链式调用嵌套三层后,连我自己都看不懂。产品经理看我调试时疯狂打断点,还以为我在玩密室逃脱。
Kotlin 协程直接把异步变同步:
suspend fun fetchUserProfile(userId: Int): User {
return apiService.getUser(userId)
}
// 在 ViewModel 中
viewModelScope.launch {
try {
val user = fetchUserProfile(123)
_uiState.value = Success(user)
} catch (e: Exception) {
_uiState.value = Error(e.message ?: "未知错误")
}
}
suspend 函数看起来像同步代码,但底层是非阻塞的。配合 viewModelScope,生命周期自动管理,再也不用担心内存泄漏。
这对我们做项目特别友好。实验室有个 IoT 数据采集 App,需要同时拉取多个传感器数据。用协程的 async/await,几行代码搞定并发:
val temp = async { sensorApi.getTemperature() }
val humidity = async { sensorApi.getHumidity() }
val result = Pair(temp.await(), humidity.await())
对比之前用 CountDownLatch 的 Java 写法,简直是降维打击。
和 Java 互操作:别怕老项目
我知道很多人不敢转 Kotlin 是因为“老项目全是 Java,重构成本太高”。其实完全不用慌!Kotlin 和 Java 100% 互操作。
我们实验室那个分布式任务调度系统的 Android 控制端,就是 Java 和 Kotlin 混写。Java 类可以直接调用 Kotlin 的 data class,Kotlin 也能无缝使用 Java 的 OkHttp、Gson。
甚至你可以在同一个文件里先写一半 Java,再切 Kotlin 继续写(虽然不推荐)。Gradle 会自动处理混合编译。上周我花两小时把一个 Activity 从 Java 改成 Kotlin,其他模块完全不用动,跑起来丝滑如初。
面试题高频考点:这些坑我替你踩了
既然提到面试题,分享几个真实被问到的点:
lateinitvsby lazylateinit用于非空属性但不能在声明时初始化(比如 View 注入),但必须确保使用前已赋值;by lazy是懒加载,线程安全,默认单例。别混用!apply、let、run怎么选?apply:配置对象,返回对象本身(适合初始化)let:空安全操作,返回 lambda 结果run:作用域函数,返回 lambda 结果(类似 let,但用this)
我的口诀:配用 apply,空用 let,跑用 run。
协程的
Dispatchers怎么选?Main:UI 更新IO:磁盘/网络 I/ODefault:CPU 密集型(比如 JSON 解析)
别在 Main 线程做网络请求,否则 ANR 伺候。
适配与性能:别只顾着写代码
Kotlin 写起来爽,但上线前还得过几关:
- 包体积:Kotlin 标准库约 1.5MB。如果项目极度敏感,可用
kotlinx.coroutines的瘦身版,或开启 R8 混淆。 - 启动速度:冷启动略慢于纯 Java(因需加载 Kotlin 运行时),但实测差异 < 50ms,用户无感。
- 多平台适配:Kotlin Multiplatform 可以共享业务逻辑(比如网络层、数据模型),但 UI 还得各写各的。我们组正在试水,效果不错。
对了,上个月发版时差点翻车——华为某机型因为 ProGuard 规则没配好,Kotlin 的 data class 被混淆导致 JSON 解析失败。后来加上:
-keep class kotlin.Metadata { *; }
-keepclassmembers class **$** {
public static ** Companion;
}
才搞定。所以说,教程里教的只是理想情况,真实世界全是坑。
最后:Kotlin 不是银弹,但值得拥有
写了两周 Kotlin,最大的感受是:它不会让你写出“更聪明”的代码,但能让你少写“更蠢”的代码。
在实验室这种既要赶项目又要准备秋招的环境下,Kotlin 的简洁和安全特性简直是救命稻草。上周给合作企业演示时,对方技术负责人看到我们的代码结构,直接问:“你们是不是有专职 Android 工程师?” 我笑笑没说话——研二狗的倔强罢了。
如果你也在犹豫要不要学,我的建议是:别等“有时间”。找个周末,把旧项目的一个小模块改成 Kotlin,跑通就行。你会发现,那些曾经让你熬夜 debug 的 NPE、回调地狱、冗长样板代码,突然就消失了。
就像我导师说的:“工具不重要,解决问题才重要。” 但有时候,换把趁手的工具,真的能少掉几根头发。
(写完这篇,我去改下一个 Activity 了。产品经理刚在群里说,“首页加个动效吧,就那个很火的 Lottie”…… 救命。)

评论 0