遇见
首发于遇见
你为什么需要 Kotlin

你为什么需要 Kotlin

本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:你为什么需要 Kotlin

导语

在当今的互联网时代,新技术犹如雨后春笋般层出不穷。精神哥之前也和开发同学一起讨论过程序员的成长离不开哪些软技能?当时很多人都有提到探究新技术对程序员的重要性。

精神哥身边就有一位热衷于探究新技术的同事——Benny(霍丙乾)。相信经常关注Bugly的同学对他一定很熟悉了,我们之前推送过好几篇Benny写的干货文章,如“深入理解 ButterKnife,让你的程序学会写代码”,还有“深入浅出 Retrofit,这么牛逼的框架你们还不来看看?”,等等……

这期咱们再跟随Benny一起来看看“Kotlin”是什么。

一、往事

曾经你有段时间研究 Intellij 的插件开发,企图编译 Intellij Idea Community Edition (ICE)的源码,结果发现有个奇怪的东西让你的代码无法编译。。什么鬼,kt 是什么玩意儿?

怎么又有新语言出来啊,简直要疯掉了。这时候,你的脑海里面瞬间浮现出了这句话:

有困难要上,没困难制造困难也要上。

『靠,这尼玛究竟是谁说的,好有道理!』你调侃道。

为了不丢掉社会主义新青年勤奋刻苦的优良传统,你决定学一下 Kotlin,不过说真的,这决定也是坑苦了自己,毕竟那段时间 Kotlin 的 API 还没有趋于稳定,经常从网上找到个 demo,搞到本地就编不过去,哭死。直到 2016年2月,Kotlin 1.0 正式发布,凌乱的 API 也随着曾经躁动的心的平静而稳定下来,你无需再忍受什么,甚至还有了一种『终于看着娃长大了』的感觉。

二、消失的 Getter 和 Setter

你一直很喜欢 Java,就像你一直喜欢喝黑咖啡一样。原因也很朴实,因为你别的也不怎么会啊。可有一阵子做一个语音聊天的 app,里面各种用户、通话记录等等的数据结构,简直了,写起来长长的一串,光 Getter 和 Setter 就一眼望不到边,每写一个数据结构类,仿佛眼前就是那金黄色的稻田,你吹一口咖啡,它们居然还簌簌作响。最讽刺的是,你发现大家在写有关 Java 的文章的时候,遇到数据结构实体类,经常会这样写:

 public class Person{
     private int id;
     private String name;
     //瞅啥瞅,省略掉的是 getter 和 setter!
     ...
 }

额,这么重复的代码居然占用了 80% 的篇幅,也难怪是个 IDE 就有帮忙生成 Getter 和 Setter 模板的功能。

你说你曾经试图不写 Getter 和 Setter,可作为一个写 Java 这么多年的人,没了 Getter 和 Setter 让你感觉就像是。。。

那时候你看到 C# 里面的属性也真是眼馋呐,『怎么 Java 就不能搞这么个特性呢?』

后来,你发现 Kotlin 居然是有属性的:

 data class Person(val id: Int, val name: String)

你眼前一亮,放下手中的咖啡,幻想着啥时候去 Kotlin 小岛玩一趟。

三、又见空指针

你的项目都接入了 Bugly,那赶脚就好像在鸡蛋上跳舞哇,每天去打开崩溃统计都心惊肉跳的。这些 crash 里面绝大多数都是空指针异常,这倒不是说空指针本身有什么问题,空指针只能说明程序有考虑不周的情形出现,出现空指针调用通常都是代码的编写问题,那么为什么 Java 会允许潜在的空指针存在呢?你望着窗外,思索着一定是什么蒙蔽了你的双眼,让你看不透,望不穿,寻不见。

 person.setName("橘右京");

回过神来,突然看到这行代码,你嘟囔着,这哥们名字叫 橘右京,可这哥们是谁?万一他是个 null 呢?

 Person person = findPersonFromCacheOrCreate();
 person.setName("橘右京");

你的目光往上移,看到了上面那行赋值,你开始怀疑,开始问自己:『
害怕不。。其实我们一直都没有意识到,我们的 Java 代码 处处不让我们放心。』

说来也巧,你最近在梳理项目代码的时候,见到的最多的就是:

 if(x != null) x.y();

你对你的代码充满了不信任,你的代码会回报你什么呢?

『虚拟机空指针全家桶来一份!。。。那一定很好吃吧』你一脸无奈的自嘲着。

你突然发现这代码居然看上去跟窗外的世界很像,只是,给代码用的净化器应该是什么牌子的呢?

『小米的怕是不行了吧。』你哈哈一笑,似乎对此感到很开心。

那开心转瞬即逝,你不得不面对这令人苦恼的现状。你在 Java 当中除了对自己说『我保证 findPersonFromCacheOrCreate() 不会返回空』,还有什么更让人踏实的办法么?当然没有。

『看看 Kotlin 有没有好办法吧!』于是你尝试着用 Kotlin 写下了类似的代码:

 fun findPersonFromCacheOrCreate(): String{
     ...
 }

当你企图在这个方法中返回 null 时,聪明的 IntelliJ 立即在你的代码上画出红线,告诉你不要这样。

你查了下资料,发现原来在 Kotlin 当中, String 表示一个不可为 null 的字符串类型。这一刻,你的内心感到无比踏实:

 val person = findPersonFromCacheOrCreate()
 person.name = "橘右京"

写 findPersonFromCacheOrCreate 这个方法的人必须给你保证返回的 person 不为 null,他在编写这个方法的时候就要百般小心,不然编译器就要削他了。

『找削袄!』你突然想起微信里面的那套 桃子一家东北话 的表情,想想就好欢乐。

『不过』,你又想,『万一它还是给我返回了 null 怎么办呢?』说着你按照 Kotlin 的要求改了下代码:

 fun findPersonFromCacheOrCreate(): String?{
     ...
 }

结果发现下面的第二行报错。

『什么情况?』你不明白了。

 val person = findPersonFromCacheOrCreate()
 person.name = "橘右京"

Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?

『就是说,如果有人胆敢给我返回个可空类型,我必须做判空处理才可以行呗?』你眼前一亮,旋即惊呼道,『太厉害了!』

紧接着你发现,虽然返回的是可空类型,但这丝毫不会影响你与你的代码谈笑风生,因为 Kotlin 可以给你一千种选择让你的代码看起来犹如行云流水一般,比如你希望拿到 null 直接返回,你就这么写:

 val person = findPersonFromCacheOrCreate()?:return

或者这里没办法用 return,你一样可以确保 person 不为空的时候执行你的逻辑:

 val person = findPersonFromCacheOrCreate()
 person?.let{
     //在这里直接用 it 指代 person,绝对不为空
 }
 person?.setName("安琪拉")//只有person不为空的时候执行

四、Smart Cast

『听说风已经到张家口了。』亚瑟说。
『哦是么,那我们可真得拭目以待呀。』你淡定地说。
『待什么,眼前一亮吗,哈哈。』也乐了。
『是啊,不过,眼下我倒更希望 Java 能聪明一点儿。。』
『它怎么了?』
『你看,Java 有一种特别缺心眼的写法:

 if(view instanceof ViewGroup){
     ((ViewGroup) view).addView(child);
 }

强转加方法调用,两对括号,写到手抽筋啊。可我已经告诉 Java view 是 ViewGroup 了啊,结果还是要强转,这种感觉就像我坐地铁的时候本来刷卡进站,结果到了车上,还有人查票!』说完,你忽然觉得口渴,随手拿起热乎的咖啡喝了一口,你眉头一皱,那味道似乎不太好。

『虽然我们写代码应该尽量避免强转,可你明明知道这东西我们无法避免,于是本来想多态的用父类或者接口引用实例,结果强转代码写得多到变态。要是我在判断了 view 的类型之后,在这个类型判断有效的作用域内不用做强转就好了。』你接着说道,一脸的烦恼。

『Kotlin 可以呀,Kotlin 有个特性叫 Smart-Cast,你写的代码就可以像这样:

 if(view is ViewGroup){
     view.addView(child) // 现在 Kotlin 已经知道 view 是 ViewGroup 类型了!
 }

咋样,厉害吧?』亚瑟一脸神气的表情。

『真的呀!』你兴奋的说。

『这你都不知道?』在一旁打游戏的太2真人一如既往地调侃道,他看上去似乎比亚瑟更得意。

五、打日志

『你们连日志都打,真不要脸。。对了,日志是谁,打的时候叫上我啊!』你露出一脸的坏笑。

原来,你有个函数传入了三个参数,

 void check(ArrayList<String> list, String tag, int id);

你想把他们的值打印一下,于是你不假思索地敲出了一行代码:

 System.out.println("list: "+list.size()+"; tag="+tag+";id="+id);

这样的语句并没有引起你的任何不适——毕竟,你早已习惯了它的丑陋。你稍稍停顿,活动了一下手指,突然想到那个经久不衰的段子:

女神:你能让这个论坛的人都吵起来,我今晚就跟你走。
程序猿:PHP语言是最好的语言!
论坛炸锅了,各种吵架。
女神:服了你了,我们走吧,你想干啥都行。
程序猿:今天不行,我一定要说服他们,PHP语言是最好的语言。

『难怪 PHP 是最好的语言,没有之一呢!』你自嘲着,『至少人家支持字符串模板呀。』

 $size = count($list);
 echo "list: $size; tag=$tag; id=$id";

说来也是好奇,这么好的特性 Kotlin 有没有呢?这家伙最近给你带来的惊喜是在太多了,于是你决定试一试。

 println("list: ${list.size}; tag=$tag; id=$id")

『嗯————』你满意的点了点头。

六、再见,Utils

你肯定用过 String,不仅如此,你还知道 String 居然连个 empty 方法都没舍得提供,每次都要写 str.equals(“”) ,真是无比丑陋。这还算好的,如果恰好要判断不为空呢?这个应该更常用吧,于是你就会写 !str.equals(“”),搞不好几个字符敲完了发现,尼玛,还得加个小括号 (!str.equals(“”))。。。

『怎么可以这么反人类!!!』你嘟囔着,一气之下写了个工具类:

StringUtils.java

 public class StringUtils{
     public static boolean notEmpty(String str){
         return !"".equals(str);
     }
     public static boolean isEmpty(String str){
         return "".equals(str);
     }
 }

你满意的点点头,觉得这样就再也不用忍受那些丑陋了。

随后的某一天,阳光依旧试图穿过层层雾霾照到你那北向的屋子的楼的正面,无果,于是垂头丧气的点了一支烟。而你呢,正要写下一句叫做 if(StringUtils.notEmpty(str)){ … } 的代码,你很快地写完 if(str. 疲惫的双眼企盼着 IDE 意会你的小眼神,可它蒙了,String 并不曾有一个叫做 notEmpty 的方法啊。这时候你迟疑了一下,缓过神来,灵活的把右手小拇指从键盘上的 ;移到 backspace,删掉 str. 重新写出 if(StringUtils.notEmpty(str))。多么令人苦恼的经历啊,于是你惆怅的点了一支烟,屋里的空气净化器也开始飞速的转了起来。

『我要是能重写一下 Java 的 String 类好了,我一定先给它加上这俩方法!!』

这时,只见一道亮光闪过,你的窗户上映出了几行字:

你惊喜的差点儿喊出声来。『这真的是 Kotlin 吗?』你有点儿不敢相信自己的眼睛。是的,有了扩展方法,你再也不需要什么 XXXUtils 了。

七、晚安,ButterKnife

『晚安。』你轻轻地对 ButterKnife 说到。你知道这也许是最后一次这样说了,毕竟在 Kotlin 的世界里,ButterKnife 开始变得有些不知所措。

『你不需要我了。』ButterKnife 有些疲惫。
『不,你是最棒的。』突如其来的这么一句话,让你显得有些慌乱。
『kotlin-android-extensions 还不够么?』ButterKnife 不耐烦了。
『可。。可是。。。』你不知道该怎么回答了,毕竟在 Kotlin 出现之后,你很少提及 ButterKnife 了。

是啊,过去你的 View 都是用 ButterKnife 注入的:

 @BindView(R.id.nameView) TextView nameView;
 ...
 nameView.setText("橘右京");
 ...

而现在呢?你摇摇头,显得有些无奈。『再也不需要注入 View 了是么?』你在问自己,尽管对过去百般不舍,可你还是很欣赏你的代码现在的样子:

 nameView.text = "橘右京"

再也不需要 ButterKnife 了,更不需要什么 findViewById 了——这就是命。

八、请和我的代理谈吧

听说有人把 SharedPreference 先生告上法庭了,说他总是在接受新值之后不去做持久化,SP 先生却觉得很委屈:

『先生们,大家都是绅士,我实在想不出什么能比我的信誉更重要。如果你们在给我们更新值的时候调用 commit,我们一定会按照约定完成持久化的。可你们为什么就不愿意 commit 呢?』SP 先生大惑不解。

『请问 SP 先生,我是 《Dalvik 日报》记者,我想问一下,为什么必须要 commit 呢?』
『您好,这是规定。』SP 先生慢条斯理的回答道。
『可这有点儿反人类呀!』记者追问道。
『没关系,基于此次诉讼事件的教训,我们特意引进了高科技人才 Preference< T > 先生,P先生在这方面可是行家了。』

记者打量着这位 P 先生,试图从他身上发现点儿什么。

『哈哈,P 先生是一位 Delegate 吧,SP 先生就是 Delegated Properties 咯~』你从人群中走出来,淡定的说道。

『不错,』P 先生扶了扶眼镜,『这位先生,SP 先生的操作方案是有些繁琐,我们来看个例子:

 context.getSharedPreferences("name", Context.MODE_PRIVATE)
     .edit().putString("key", "value").putInt("intKey", intValue).commit();

一位绅士需要操作它的时候,总是首先要获取 SharedPreferences 实例,接着 edit 拿到 Editor 实例才能存入值,一方面存入的值存在随意性,key 的值必须约束好才行,否则读取方就无法获取到值,另一方面只有 commit 之后值才可以被存入。这样操作起来确实不是很友好。』

『那 P 先生高见?』你很好奇。

『从今天起,大家如果有需要 SP 先生持久化数据的需求,只需要在我这里登记一次,剩下的,大家只需要像读写变量一样操作即可生效。』P 先生停顿了一下,打量了一下四周,大家都在注视着他,而一旁的 SP 先生也给了他一个坚定的眼神。

『那么以后,如果有位绅士需要我们,比如他需要持久化的数据名叫 “name”,值叫 “橘右京”,当然这个值也是可以修改的,那么他只需要这样操作:

 var name by Preference(context, "name", "橘右京", "sp_name")
 ...
 Log.d(TAG, name) // 第一次读取,只能读取到默认值,那就是 橘右京
 name = "不知火舞"
 Log.d(TAG, name) // 这里输出的就是 不知火舞 啦

我们真正做到了读写持久化数据就如同读写内存变量一样简单直接。』

『真的好赞。』你不禁鼓起掌来。『那 P 先生,我能读一下你的源码么?』

……

突然,你的手机振动了一下,打断了你的思绪。你从沉思中回来,发现你眼前不过仍然是你的 IDE,而屏幕上的这段代码,正是 P 先生的源码。真的是太巧妙了:

 class Preference<T>(val context: Context, val name: String, val default: T, val prefName: String = "default") : ReadWriteProperty<Any?, T> {
     val prefs by lazy { context.getSharedPreferences(prefName, Context.MODE_PRIVATE) }
     override fun getValue(thisRef: Any?, property: KProperty<*>): T {
         return findPreference(name, default)
     }
     override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
         putPreference(name, value)
     }
     private fun <U> findPreference(name: String, default: U): U = with(prefs) {
         val res: Any = when (default) {
             is Long -> getLong(name, default)
             is String -> getString(name, default)
             is Int -> getInt(name, default)
             is Boolean -> getBoolean(name, default)
             is Float -> getFloat(name, default)
             else -> throw IllegalArgumentException("This type can be saved into Preferences")
         }
         res as U
     }
     private fun <U> putPreference(name: String, value: U) = with(prefs.edit()) {
         when (value) {
             is Long -> putLong(name, value)
             is String -> putString(name, value)
             is Int -> putInt(name, value)
             is Boolean -> putBoolean(name, value)
             is Float -> putFloat(name, value)
             else -> throw IllegalArgumentException("This type can be saved into Preferences")
         }.apply()
     }
 }

『Impressive.』你一副恋恋不舍的样子。『 SP 先生的全名应该不是 SharedPreferences 吧,我觉得他们一定是搞错了,应该是 SophisticatedPreferences 还差不多。P 先生自然就是简化的它咯。』

九、壮士一去兮为啥不复还?

这个问题你想了很久。是荆轲的匕首不够快?还是不够长?

『总之是不好用呗。』你嫌弃地说。

Java 里面也有一副利刃,叫做 Dagger,这把利刃可以帮你生成一些代码。

『如果代码可以聪明到自己写代码,那我们不就要失业了吗?』不知道你说这话是在调侃,还是感到有些恐慌。

Kotlin 之前是无法使用这把利刃的,这可能真的打击了不少人的积极性。不过,这已经不是问题了,因为你在前不久读到 Kotlin 1.0.4 的更新说明的时候,就已经发现 kapt 的存在。只要你添加 apply plugin: “kotlin-kapt” 这句配置,你就可以像在 Java 当中一样使用 Dagger 了——你甚至还做了个 demo 试了一下,程序员嘛,总是无法摆脱成功写出一个 Hello World 程序时获得的内心的愉悦感。

『似乎除了 FindBugs 之类与 Java 语法紧密结合的框架不能直接应用到 Kotlin 上,别的都没有什么问题哎。』你似乎发现了什么。

Java 和 Kotlin 的对话

『Java 叔叔,我。。我怕。。。』Kotlin 怯懦的说。
『有叔叔在呢。』Java 拍着胸脯,安慰道。『世界是你们的,也是我们的,但是归根结底是你们的。你们青年人朝气蓬勃,正在兴旺时期,好像早晨八九点钟的太阳。希望寄托在你们身上。孩子,放手去干吧,搞不定的,找叔叔,叔叔虽然老了,但叔叔还是有自信能帮你搞定。』

你突然回过神来,『我脑洞怎么这么大,哈哈哈~』

Java 和 Kotlin 的对话 2

『Java 叔叔,我。。我怕。。。』Kotlin 怯懦的说。
『有叔叔在呢。』Java 拍着胸脯,安慰道。『世界是你们的,也是我们的,但是归根结底是你们的。你们青年人朝气蓬勃,正在兴旺时期,好像早晨八九点钟的太阳。希望寄托在你们身上。孩子,放手去干吧,搞不定的,找叔叔,叔叔虽然老了,但叔叔还是有自信能帮你搞定。』
『好的,叔叔,那我就不客气了!』Kotlin 嬉皮笑脸的样子真得很像个小孩子。
『我去,居然装可怜骗你叔叔,看我不打你!』Java 气急败坏道。

『完了,脑洞合不上了。。』你已经陶醉了其实。

在你看来,Kotlin 似乎并不是一门新的编程语言,它看上去更像 Java 的语法糖,只不过,这糖放的彻底了些。

『嗯。。挺甜~』你喝了口咖啡,不过这次不同以往,你放了糖。

更多精彩内容欢迎关注腾讯 Bugly的微信公众账号:

腾讯 Bugly是一款专为移动开发者打造的质量监控工具,帮助开发者快速,便捷的定位线上应用崩溃的情况以及解决方案。智能合并功能帮助开发同学把每天上报的数千条 Crash 根据根因合并分类,每日日报会列出影响用户数最多的崩溃,精准定位功能帮助开发同学定位到出问题的代码行,实时上报可以在发布后快速的了解应用的质量情况,适配最新的 iOS, Android 官方操作系统,鹅厂的工程师都在使用,快来加入我们吧!


编辑于 2017-01-16

文章被以下专栏收录