Android 开发者再不转Kotlin,真的来不及了

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,早点从那些繁琐的破事里解脱出来

相关推荐
赏金术士5 小时前
第五章:数据层—网络请求与Repository
android·kotlin·compose
初雪云5 小时前
让安卓发版再简单一点,体验一键自动化发布
android·运维·自动化
jushi89996 小时前
抖音APP抖音助手增强版 内置逗音小手 支持无水印下载/音频提取/去广告等功能
android·智能手机·音视频
plainGeekDev6 小时前
Android 专家岗 Kotlin 面试题:能答出这些,说明你对语言设计有自己的理解
android·kotlin
plainGeekDev6 小时前
Android 资深岗 Kotlin 面试题:只会用协程不够,你得懂它为什么这么设计
android·kotlin
StarShip7 小时前
第一阶段:应用层视图绘制
android
StarShip7 小时前
第二阶段:RenderThread 渲染处理
android
通玄7 小时前
Jetpack Compose 入门系列(一):从零搭建到基础控件使用
android