Kotlin初体验

Posted by Bo on March 25, 2018

其实早在2017年初就知道Kotlin了,只不过一直没研究过。那个时候Google IO还没开,Kotlin和Android还处于地下恋情阶段。Kotlin的主页上有一行大字:

100% interoperable with Java

然而那并没有什么卵用,没有人会去注意它,毕竟这个世界上的语言多如牛毛。哪怕是写下这篇文章的2018年3月,Kotlin也只能勉强挤进编程语言排行榜的前50。直到2017年的Google IO宣布Android支持Kotlin。

1

Kotlin的Google Trend突然迎来了火箭般的上升,所有人都在问,Kotlin是个啥?

1

在Gradle内部,有一个组一直在开发Kotlin DSL,经常有人在Slack群里抱怨写了Kotlin就回不去了。这周末我花了一天时间看了一下Kotlin的文档,也写了一个100% Kotlin的项目练手,详见我的这篇文章JUnit 5 Unroll Extension for Kotlin

语法奇怪

Kotlin给人的第一印象是语法很奇怪,经常写着写着就不由自主地写成了Java,还不禁纳闷,咋红了呢?对于我这个初学者来说,根本不可能在离线状态下写Kotlin项目——许多问题文档里面找不到,要去Stack Overflow或者Kotlin的论坛里面找。熟悉之后就好多了,不是什么大问题。

写起来爽

抛开语法,Kotlin写起来确实很爽,和IDEA搭配的手感自然是没的说,毕竟是亲儿子。那感觉就像是……想挠痒痒的时候,按下Alt+Enter键,IDEA就把痒痒挠放到了痒的地方,只要再按一下Enter就能挠个痛快。总体上来说,写Kotlin就像是在写Java 8 + Groovy的杂交品种。它是完全静态强类型的,比Groovy不知道高到哪里去了。写多了动态语言的人都知道:

动态语言一时爽,事后debug火葬场

1

它的闭包系统和Groovy非常像,却又能和Java的Lambda表达式/方法引用无缝衔接,这是我觉得最厉害的。函数式编程和Stream结合在一起,写起来行云流水,很舒服。其实这并不是非Kotlin不可,纯Java也能做到,但Kotlin加了一些新料让这个过程更顺畅。比如说Kotlin的also:

private fun getParam(whereFunctionClass: Class<*>): Param {
    return Param().also { getFunctionInstance(whereFunctionClass).invoke(it) }.also { verifyArguments(it) }
}

翻译成Java大概是:

private Param getParam(Class<?> whereFunctionClass: Class<*>) {
    Param ret = Param();
    getFunctionInstance(whereFunctionClass).invoke(ret);
    verifyArguments(ret);
    return ret;
}

这只是一个例子,Kotlin中有不计其数的语法特性逼迫你用函数式思维思考并编程,这样带来的结果是更加优雅的代码。

至于后来的维护者能不能看懂……随缘吧。

Extension

Groovy的核心是元对象协议(MOP),说白了就是当调用一个对象的方法/属性的时候,Groovy先进行一次运行时查找,找到了,就调用之,否则报错。很明显,这个机制是一柄双刃剑:我们可以动态地往Groovy里面添加方法,却失去了编译检查带来的安全。

而Kotlin是静态的,不可能往里面动态地加东西。老师老师,那我特别想给已经存在的类加一些方法,比如下面这个方法,咋办?

assert("  ".isBlank())

Java的答案是StringUtils.isBlank(String s)。在Java世界里,随便打开哪个项目,搜索一下,你都能看到几个十几个不等的StringUtils类。

这么不优雅的写法,你说Kotlin能忍么?它搞出了一个Extension机制。对于上面这个例子,Kotlin的解决方案是:

fun String.isBlank(): Boolean {
    ...
}

然后就可以直接对字符串对象调用isBlank()了,就如同这个方法是它自带的一样。

糖特别多

Kotlin的语法糖多到令人发指的地步。总结一下就是,编译器能做的事情绝不让你做。举个例子:

public boolean isEmptyString(Object obj) {
    return (obj instanceof String) && ((String)obj).isEmpty();
}

Kotlin说,费那事干啥!&&后面的那个转型不是脱了裤子放屁么!

public isEmptyString(obj: Any): Boolean {
    return (obj is String) && obj.isEmpty() // 在&&后面自动转型为String
}

BTW,对于这种一行的函数,可以简化成:

public isEmptyString(obj: Any): Boolean = (obj is String) && obj.isEmpty()

在各种分支中,只要是编译器能推断出来,所有的转型、空指针检查统统不用你自己做。你如果非要自己做的话,IDEA会提示你,这个地方可以优化哦亲!

上手难度

Kotlin上手还是有一定的门槛的,至少我觉得,以四年前我刚进阿里的时候的代码经验应该驾驭不了,尤其是by这种高级用法……我觉得这货最适合有几年编程经验的人(无论什么语言)。新人还是老老实实码一段时间的Java比较合适,不要一上来就想搞个大新闻。要排个序的话,

Java > Groovy > Kotlin

什么,你说Scala?我还是讲个故事吧——年前跟LinkedIn的一个小伙聊了两句,因为LinkedIn的主力Web框架是Play,我特好奇,你们是怎么招到写Scala的人的?

对方回答,因为Scala隐藏的坑太多,现在都强制写Java了……

这个故事告诉我们,在每门光鲜亮丽、说的比唱的还好听的JVM语言背后,都有无数不为人知的大坑。如果没有老司机带路的话,玩玩就行,别把自己身家性命压上……可能会翻车的。

Gradle DSL

JUnit 5 Unroll Extension for Kotlin的开发过程中,我尝试完全用Kotlin DSL配置Gradle,磕磕绊绊总算是成功了,期间请教了隔壁组的Paul好多问题。用Kotlin写Gradle DSL最大的问题其实不在于它的强类型,而在于……几乎所有的第三方插件给出的demo都是Groovy的格式,需要人肉翻译成Kotlin,有的甚至要去翻插件的源代码……

不过有一点可以确定,虽然kotlin DSL还没到1.0版本,但是生产上使用已经没啥问题了,Gradle项目的构建配置*.gradle已经全面转向了*.gradle.kts

有关Groovy和Kotlin配置Gradle构建的事情,下次专门开一篇帖子讲吧。