kotlin 开篇
Kotlin 开篇
这篇文章是为「码上开学」写的,首发于码上开学官网 。
引子
找到那把三叉戟,你便能号令整个海洋。 ——《海王》
为什么需要新语言
Java是当今世界最流行的工业级语言,有着非常成熟的生态和广泛的开发群体。当初Android选择Java作为开发语言,也是为了吸引Java程序员这个世界上最大的开发群体。最早的一批Android程序员可能已经用Java写了近十年Android程序了,各种实践方法也比较成熟了。那么今天我们为什么还需要一门新语言呢?这就要从Java的发行历史说起了。
Java历史
Java版本 | 发布日期 | 重要功能 |
---|---|---|
JDK 1.0 | Jan 1996 | Initial release |
JDK 1.1 | Feb 1997 | Reflection, JDBC, Inner Classes |
J2SE 1.2 | Dec 1998 | Collection, JIT |
J2SE 1.3 | May 2000 | HotSpot JVM, JNDI |
J2SE 1.4 | Feb 2002 | assert, Regular, NIO |
J2SE 5.0 | Sep 2004 | Generics, Annotations, Autoboxing/unboxing, Concurrency |
Java SE 6 | Dec 2006 | JDBC 4.0, Java Compiler API, New GC |
Java SE 7 | July 2011 | Strings in switch, Diamond operator, Resource management |
Java SE 8 | Mar 2014 | Lambda, Functional interface, Optionals, New Date and Time API |
Java SE 9 | Sep 2017 | Multi-gigabytes heaps, Java Module System |
其实从Java 5开始,Java就是一门很完备的工业级语言了,生态也非常成熟,各种框架层出不穷。但是后续的Java 6和Java 7只是在Java 5基础上添加了一些功能和语法糖,根本算不上大版本更新,我觉得版本号定为5.1,5.2可能更合适。如果你稍微了解过同期的C#,看看Lambda和LINQ,你大概也会对Java怒其不争吧。
直到2014年Java 8正式发布,才算是Java语言的一次大更新,加入了呼声很高的Lambda和stream。可是等等,Android是哪一年发布的?2008年!所以Android是以Java 6来进行开发。虽然从Android Studio 3.0开始可以使用部分Java 8特性进行开发了,但是非常好用的stream只能在Android 7.0以上使用(即minSdkVersion = 24)。受困于Android版本碎片化,我们只能放弃。那么用Java 6写代码到底有什么问题呢?
从一道算法题说起
请听题:有一个英文小写单词列表List\<String>,要求将其按首字母分组(key为 ‘a’ - ‘z’),并且每个分组内的单词列表都是按升序排序,得到一个Map\<Character, List\<String>>。请尝试用10行以内Java 6.0代码完成。
1 | List<String> keywords = ...; |
实际上已经超过10行了。我们再看看业界标杆C#是怎么写的
1 | List<string> keywords = …; |
为了代码可读性,我添加了一些换行。如果你愿意的话,写成一行也行。可以明显看出,对比当今先进语言,Java 6已经无法让人愉快的写代码。用Java 6写代码时,我们脑子里想的不是要做什么,而是怎么做。
Java平台
我们平时说Java,其实包含了两个不同的概念:一是Java语言本身,二是Java虚拟机,即JVM。虽然上面吐槽了Java语言本身的历史包袱,但是JVM还是非常优秀的,它有非常多的优点:
- 跨平台
- 自动垃圾回收
- 运行速度快(你没看错。虽然Java程序速度没有C/C++快,但是在所有编程语言中Java属于速度快的那一拨。大数据框架Hadoop便是用Java开发,能顺利处理海量数据。)
我们知道Java代码编译后会生成字节码,然后字节码在JVM中运行注意Android中的虚拟机并不是JVM,而是Dalvik/ART,但也是编译成字节码。那么有没有可能新创造一门语言,编译的时候也生成字节码,然后在JVM中运行呢?这样既可以摆脱Java的历史包袱,又能享受到JVM和成熟Java框架的各种好处!答案当然是肯定的!实际上Java平台已经衍生出Scala、Clojure、Groovy等比较流行的语言了。而今天我们要讲的Kotlin则是Java平台中的新贵,出自大名鼎鼎的JetBrains公司。打开他们的官网你就会发现,很多著名的IDE(比如IntelliJ、RubyMine、WebStorm)都是出自这家公司。IntelliJ是当今最主流的Java IDE,JetBrains公司在开发IntelliJ的过程中积累的经验令Kotlin的诞生显得水到渠成。而Google在2017年的I/O大会上宣布Kotlin成为Android开发的官方编程语言后,更是令Kotlin一夜之间成为最受瞩目的编程语言之一。那么我们来看看Kotlin会给我们带来哪些好处吧。
Kotlin带来的好处
Data Class
假设你在做一个金融交易系统,需要定义一个Class来表示每一笔支付,包含金额和币种。这里有一个简单的例子:
1 | public class Purchase { |
看起来不错。但是作为一个经验丰富的程序员,你一眼就发现了潜藏的问题。一个金融系统,一定是要保证每笔交易的正确性的,你肯定不希望object中的currency和price的值在某个模块里面被粗心的人修改了。所以你需要一个Immutable Class,即不可更改的类。于是你做了如下修改:
1 | public class Purchase { |
你将currency和price定义为private field,通过构造方法来赋值,并且只暴露get方法。这样就不用担心数据被其他人误修改了。完美!可是等等,这就够了吗?熟读《Java编程思想》的你立刻想起,一个完备的Class还需要重写equals()
、hashCode()
和toString()
方法!于是你又添加了一些代码:
1 | public class Purchase { |
我就问你烦不烦:) 仅仅是表示金额和币种,怎么要写那么多代码?如果有十几个字段,那还不得上百行代码呀?!请看Kotlin中优雅的实现:
1 | data class Purchase(val currency: String, val price: int) |
没了。
一行代码搞定:)
关键字val
会自动为currency和price创建不可更改的field,而data
则会帮我们自动生成equals()、hashCode()和toString()方法!这还不是全部,看下面的代码:
1 | val iPhone8 = Purchase(“CNY”, 5888) |
看到copy
方法了吗?是不是巨好用?这也是data class为我们自动生成的,方便吧:)
Extension Functions
Kotlin中的Extension类似于Objective-C中的Category,可以帮助我们扩展已有的Class,而无需继承这个Class,无论我们能不能访问源码。这对于系统Class以及一些第三方library中的Class特别有帮助。
随着项目规模的扩大,你的代码里肯定少不了一系列Utils类(比如FileUtils.java
,DateUtils.java
)来封装一些繁琐但常用的功能。比如在Android开发中,如果我们要动态的调整一个View的宽高,必须要先将dp转为px,所以我们会有这样一个方法:
1 | public class ScreenUtil { |
然后这样使用:
1 | layoutParams.width = ScreenUtil.dip2px(16F) |
这样看起来没什么不对,只是不太符合人的阅读习惯。看看用Extension能帮我们做些什么:
1 | fun Int.toPx(): Int { |
我们给整数类型添加了一个Extension Function,叫做toPx
,用来实现dp到px的转换。然后优雅的调用:
1 | layoutParams.width = 16.toPx() |
可读性是不是好多了🙂。你可能会想,Extension一个一个自己写也挺麻烦的,有没有大神把常用的Extension Functions封装成一个library啊?有的,Google爸爸已经帮我们考虑到了😘。请参看Android KTX项目。这个项目是Android Jetpack的一部分,而Jetpack也是我们接下来要分享的内容。
Null Safety
这是一个值得吹上三天三夜的革新。在Quora上有这样一个问题:为什么空指针异常被称为10亿美金的错误?相信每一个Java程序员都对这个问题深有体会。看看我们为了避免程序崩溃,不得不写多么丑陋的代码:
1 | if (a != null && a.b != null && a.b.c != null) { |
这样写有两个问题,一是代码很丑陋,二是本少爷很容易忘记做空指针检查啊😤!!!Java 8引入了Optional来解决这个问题,比自己检查空指针要稍微优雅一点,但仍然有许多不必要的代码包装。
Kotlin的类型系统从一开始就致力于避免空指针异常。我们在Kotlin中定义变量时可以指定它为可空类型(Nullable Type)和不可空类型(Non-Null Type)。
1 | var a: String = "abc" // 不可空 |
上例的a是Non-Null,而b是Nullable。他们的唯一区别是定义的时候b的String
后面加了个问号?
,代表它是Nullable的。只有Nullable类型可以赋值为null。调用Nullable对象的方法时,需要加一个问号,像b?.length
。如果b为null,那么print(b?.length)
这行代码不会被执行。这样设计有什么好处呢?回到我们上一个例子,如果我们将a
,a.b
,a.b.c
都定义为Nullable,那么就可以这样写:
1 | println(a?.b?.c?.toString()) |
如果它们中间任意一个为null,那么 a?.b?.c?.toString()
这个 chain 就会返回 null 。是不是很强大很方便?理论上讲,只要我们的类型定义合理,那么90%的空指针异常都是能避免。为什么我不敢说100%?因为有时候我们看设计文档确定某个值绝对不可能为null,于是给它定义为Non-Null,结果程序运行时偏偏就传过来一个null……😂
Courtines
Android开发中多线程处理一直是一个难点,稍微不小心就容易出问题。你可能已经学习过RxJava,并在项目中成功使用RxJava来处理线程问题。这非常好。但是如果你的业务逻辑并没有复杂到必须用RxJava来解决,你应该看看Kotlin中的Courtines。Courtines通常翻译成_协程,在Lua等程序语言中已经有着广泛的应用。它的概念稍微有些复杂,我们可以暂时认为它是一种无需锁_并且没有线程切换开销的轻量级线程。
我们看一个例子,假设我需要从网络取回来一些数据,然后保存到数据库,那么传统的异步回调写法是这样的:
1 | networkRequest { result -> |
而用Courtines的写法是这样的:
1 | val result = networkRequest() |
你可能已经发现了,这段代码根本不关心线程如何切换,只关心我到底要实现什么功能。实际上Courtines背后远比这要复杂,想要熟练使用需要经历一些学习曲线,但好在曲线并不算陡峭。我们后面会有专门文章来讲解如合使用Courtines。如果你已经激动的等不及了(和第一次知道Courtines时的我一样),可以先跟着Google Codelabs中的教程来练练手。
与Java的交互
虽然前面我们讲到,Java平台上的语言都会编译成字节码,但这并不代表所有语言都能与Java无缝交互。比如Scala和Java的交互就非常复杂。幸运的是,JetBrains从一开始就将与Java的交互性作为Kotlin的设计目标之一。无论是从Java调用Kotlin,还是从Kotlin调用Java,都非常自然,没有什么障碍。这意味着我们可以任意使用Java丰富的第三方库,也可以在现有的Java工程基础上用Kotlin添加新的功能。万一你遇到某些特殊情况,有一段逻辑用Kotlin搞不定(我写这篇文章时就遇到一个),可以把这段逻辑抽出来用Java写。有Java兜底,我们就能放心的为项目引入Kotlin了。
无障碍升级
我们前面讲到,要想在Android开发中使用Java 8中的stream,需要设定minSdkVersion = 24。如果将来要使用Java 9的新特性,恐怕又得等Android版本更新了。Kotlin则不同,你可以把它当作一个集成到项目中的第三方library,可以随时升级到最新版本!这意味着Kotlin将来推出的更多新特性都能应用到所有Android项目中!
迁移到Kotlin会遇到什么问题
前面讲了Kotlin的这么多好处,但要知道世上没有十全十美的语言。就Kotlin而言,目前社区反映比较多的问题是可见性修饰符(Visibility Modifiers)。我们知道Java中有四种访问权限:public
,protected
,private
和package-private
。其中package-private是指在同一个包名下可见,在library开发中非常方便。而在Kotlin中没有了package-private,取而代之的是internal
,即在同一个模块(Module)内可见。这样我们在设计library时必须对可见性控制有更周全的考虑。为Android开发library,不可避免的要重写系统方法。如果你用重写了一个Java中的package-private
方法,那么不好意思,这个方法会变成public
🤣,原本并不想暴露出来的方法暴露了……
如果你主要做应用开发,那么目前已经没有什么坑了。Google已经逐渐用Kotlin重写Android文档中的所有例子,Github上用Kotlin开发的项目也在飞快增长。社区方面,在StackOverflow做的2018年度调查中,Kotlin更是一举登上最受欢迎语言榜第二名!就像前面讲的,万一有问题还有Java兜底。所以你唯一需要考虑的可能就是团队学习成本。好在Kotlin是一门非常简易、现代的语言,相信做这个决定并不困难。
回到最开始
我想肯定有好事的人要问,最开始那个算法题用Kotlin怎么写呢?
1 | val keywords = arrayOf("apple", "app", "alpha", ...) |
你还在等什么?