Android 开发者再不转Kotlin,真的来不及了
最近在Boss上随手搜了一下Android岗位,一个很直观的感受:Kotlin岗比Java岗多出一大截。
Java岗位不是没有。但你仔细看就会发现:要么是维护老项目,要么是外包,要么写着"Java/Kotlin均可"但面试的时候问你Kotlin的比重越来越大。
和圈内的朋友聊,大家都有同样的感受:Java岗在收缩,Kotlin岗在增加,35岁+的Java老兵,正在被两头挤压。
不是危言耸听,是市场变了
先说数据,不玩虚的:
很多朋友反馈,现在面试Java岗,几乎必问这几个问题:
- "你有Kotlin项目经验吗?"
- "协程用过吗?"
- "Flow了解吗?"
剩下说不问Kotlin的,最后也都会加一句:"我们后续打算迁移到Kotlin。"
为什么?因为Google从2017年就开始强推Kotlin,2019年直接官宣Kotlin为Android首选语言。现在你去翻翻Android官方的Sample、Jetpack组件、Compose文档,代码示例几乎全是Kotlin。
当语言本身成了标配的时候,你不会,就不是"加分项"的问题了,是"有没有资格入场"的问题。
说技术。为什么我还是劝你转?
我知道你可能看过很多文章讲Kotlin好------语法简洁、空安全、协程......
但那些都是表面。
作为一个老Android开发,我想告诉你的是:Kotlin不只是语法的升级,它是思维方式的重构。
1. 协程 vs RxJava/Handler:不是"代码少了",是思维模型变了
2017年我们学RxJava的时候,全网都在说"响应式编程是未来"。我也学了,写了几个Demo,感觉很爽。
然后用到项目里,三个月后:
scss
.onMainThread()
.observeOn()
.subscribeOn()
.Schedulers.io()
.compose()
.flatMap()
.switchMap()
.concatMap()
...
团队里的3个中级工程师,没一个能完全搞清楚这些操作符该往哪放。
这不是他们的问题,是RxJava本身的认知负荷太高了。一个HTTP请求+数据库缓存+UI展示,你要写一大串链式调用,新人来了还得画图解释。
协程不一样。
ini
viewModelScope.launch {
val user = withContext(Dispatchers.IO) {
api.getUser()
}
_user.value = user
}
就这4行。一个刚毕业的应届生都能看懂。
你说协程比RxJava好在哪里?不是语法简洁,是它的认知模型跟人类顺序思考的方式是一致的。
Handler我们也用了十几年了。但你有没有发现,一旦业务稍微复杂一点,回调嵌套回调,你自己写着写着都不知道哪个回调在哪个线程。
协程把"异步"这个问题彻底从语言层面解决了。你不再需要想"我要new一个什么线程",你只需要想"这段代码要不要挂起"。
我第一次觉得异步编程可以这么自然。
2. 扩展函数 vs 工具类:不是"少写几个类",是API设计能力的质变
以前我们封装工具方法怎么写?
typescript
public class StringUtils {
public static boolean isEmpty(String str) { ... }
public static String formatPhone(String phone) { ... }
public static int toInt(String str, int defaultVal) { ... }
}
然后调用:StringUtils.isEmpty(str)
很合理对吧?
但问题是:你的代码里到处都是StringUtils.、DateUtils.、ViewUtils.,满屏都是工具类。
而且当你想要扩展一个第三方库的类型时,对不起,你只能继承或者包装,改不了原类型。
Kotlin的扩展函数让这件事变了:
kotlin
fun String.isValidPhone(): Boolean { ... }
fun View.setVisible(visible: Boolean) { ... }
fun Date.format(pattern: String): String { ... }
然后你可以像调用原生方法一样调用:phone.isValidPhone()
这不只是语法糖。这是API设计思维的转变------你不再需要在"全局工具类"和"局部内聚"之间妥协。
而且,当你写SDK、写框架的时候,扩展函数让你的API可以优雅地"长"在用户已有的类型上,而不是逼用户去学一套新的工具类体系。
现在写Java的时候总觉得束手束脚------不是不能写,是写完总觉得哪里不对劲。
3. 空安全 vs 防御式编程:不是"少写if",是错误处理范式的转变
Java里我们怎么对付空指针?
scss
if (user != null) {
if (user.getProfile() != null) {
if (user.getProfile().getAvatar() != null) {
String url = user.getProfile().getAvatar().getUrl();
}
}
}
或者用Optional:
css
String url = Optional.ofNullable(user)
.map(User::getProfile)
.map(Profile::getAvatar)
.map(Avatar::getUrl)
.orElse("");
说实话,Optional已经是Java8的进步了。但它本质上还是事后补救------你还是在写"如果为空怎么办"。
Kotlin的空安全是编译期检查:
arduino
val url: String? = user?.profile?.avatar?.url
注意那个?。这意味着:
- 如果任何一个环节是null,整条链直接返回null
- 如果你试图对
String?调用length,编译器直接报错 - 如果你确定不会为空,用
!!或者提前?.let
这不是"少写if",是把空指针这个问题从运行时提前到了编译时。
你在写代码的时候就必须想清楚:这个值可能是null吗?我要怎么处理?
Java项目里有多少NPE是在上线之后才被发现的?空指针异常的根源不是null太多,是语言没有从机制上逼你面对null。
4. data class + sealed class vs Java Bean + enum:状态建模能力的天壤之别
这是我觉得差距最大、但也是最少有人提到的一点。
Java里我们怎么表示一个状态?
arduino
// 订单状态
public class Order {
private int status; // 0-待支付 1-已支付 2-配送中 3-已完成 4-已取消
private String statusDesc;
private String reason; // 取消原因
}
然后你在代码里到处写:
vbnet
if (order.getStatus() == 0) { ... }
else if (order.getStatus() == 3) { ... }
// 有一天产品说:状态4要拆成"用户取消"和"超时取消"
// 你开始改:加字段、加枚举、加if-else...
状态和业务逻辑混在一起,枚举和业务含义混在一起,你改一个状态,整个项目跟着改。
Kotlin怎么做?
kotlin
sealed class OrderState {
object Pending : OrderState()
data class Paid(val orderId: String) : OrderState()
data class Delivering(val trackingNo: String) : OrderState()
data class Completed(val rating: Int?) : OrderState()
data class Cancelled(val reason: String, val byUser: Boolean) : OrderState()
}
data class Order(
val id: String,
val amount: Double,
val state: OrderState
)
sealed class把"可能的状态"限制在一个有限的集合里,编译器会检查你处理了所有情况。
新增状态?你必须显式添加一个新的子类,编译器会告诉你哪些地方需要处理这个新状态。
而且每个状态可以携带不同的数据------"已完成"可以带评分,"已取消"可以带原因。
这是状态建模能力的差距。Java的int/enum只能表示"标签",Kotlin的sealed class可以表示"带上下文的完整状态"。
当你的业务复杂到一定程度,这两种写法的可维护性差距是指数级的。
5. Flow vs LiveData:响应式编程的正确打开方式
LiveData很好,我用了好几年。但它有个根本问题:它是Lifecycle-aware,不是真正的响应式。
LiveData只能处理"最新值",没法做背压处理,没法做复杂的转换,操作符也很有限。
Flow:
scss
// 搜索场景
searchEditText
.debounce(300)
.filter { it.length >= 2 }
.distinctUntilChanged()
.flatMapLatest { query ->
api.search(query)
}
.catch { emit(emptyList()) }
.collect { results ->
adapter.submitList(results)
}
这才是真正的响应式。而且Flow是协程的一部分,天生支持挂起,不需要额外的学习成本。
我知道Kotlin也有槽点
说了这么多,不是Kotlin什么都好。
编译慢。 这个我必须承认。大型项目的增量编译,Kotlin有时候比Java慢30秒以上。你要是电脑配置一般,等编译的时候真的可以泡杯茶。
生态问题。 有些老库只有Java版本,你得自己写Java interop。虽然90%的主流库都支持Kotlin了,但偶尔还是会遇到一两个只支持Java的。
学习曲线。 协程、Flow、扩展函数、inline function......上手容易,但想用好还是需要时间。
但问题是:这些槽点,相比Java给你的限制,真的不算什么。
继续抱着Java,不是没选择,是选择了最贵的那个
有朋友说:我Java写了这么多年,放弃太可惜了。
我理解这种心情。
但问题是:继续用Java,你以为在守着什么,其实在失去什么。
你守着的,是十几年的肌肉记忆和代码积累。
你失去的,是更好的工作机会、更高的薪资、更轻松的业务开发、以及站在语言演进这一侧的心理安全感。
技术一直在变,但有一点没变:你得愿意走出舒适区。
最后说几句
我当初也是硬扛了好久,总觉得 Java 写了十几年,闭着眼睛都能敲,学新东西又费脑子又费时间,纯粹是瞎折腾。直到被官方文档和招聘市场双重打脸,才硬着头皮开始学,结果越写越后悔 ------ 后悔没早点转。
真的不用给自己太大压力。不用逼自己一个星期就把所有语法背下来,也不用上来就想把整个老项目重写成 Kotlin。就从下一个要写的小功能开始,试着用 Kotlin 写几行,遇到问题就查,踩了坑就记下来。
没人能一开始就写出地道的 Kotlin 代码。我刚开始写的时候,全是 Java 味儿的 Kotlin,把 val 当 final 用,把扩展函数当工具类写,作用域函数乱用,闹了不少笑话。但写着写着,慢慢就找到感觉了。
我们做技术的,一辈子都在跟新技术打交道。从 Eclipse 到 Android Studio,从 Handler 到 RxJava 再到协程,哪一次不是从零开始学的?这次也一样。
早一天迈出第一步,就能早一天少写点重复代码,少出点低级 bug,早点从那些繁琐的破事里解脱出来