近半年因业务需要,带领团队成员新开发一款APP并顺利上线,目前已迭代2个版本。众所周知,2019年Google I/O大会上宣布Kotlin-first,因为项目最开始,我们便制定了APP全部代码由Kotlin实现
的目标。项目告一段落后,因此整理一篇文章,一是在团队中分享给更多成员,二是巩固已有知识。前言结束~
一. Kotlin语言的发展历史
Kotlin是一门在JVM
上运行的静态类型编程语言,也可以被编译成为JavaScript源代码
。由JetBrains开发(1200+员工)。"Kotlin"命名取自圣彼得堡附近的一座岛屿名称。Kotlin是根据Apache 2.0
授权的免费开源项目:JetBrains/kotlin
- 2011年7月,JetBrains推出Kotlin项目(已开发1年之久)
- 2012年2月,JetBrains以Apache 2许可证开源此项目
- 2016年2月,Kotlin v1.0发布
- 2017年5月,Google在I/O大会上宣布:
Kotlin为Android开发一级语言
- 2019年5月,Google在I/O大会上宣布:
Kotlin-first,成为Android开发首选语言
- 2020年3月,发布最新版本v1.3.71
JetBrains团队创建Kotlin项目的主要目标:
- 创建一种兼容Java的语言
- 编译速度至少同Java一样快
- 比Java更安全
- 比Java更简洁
为什么Kotlin会得到Google支持?
- 与Oracle旷日持久的Java侵权案:最新结果Google败诉,需向Oracle赔偿88亿美元。起因:Oracle起诉Android中无偿使用了
37个Java APIs
,侵犯专利;同时有9行代码
抄袭了Java - Kotlin自身的语言优点
Android官方Kotlin示例:
Kotlin构建的应用:
Stack Overflow Developer Survey 2019的结果中:
- 最受喜爱的编程语言排名
第4
,72.6%
(Top 1:Rust 83.5%,Java排名第18:53.4%) -
6.4%
的人使用Kotlin编程 (Top 1:JavaScript 67.8%,Java排名第5:41.1%)
二. Kotlin适用范围
重点解释下服务端、Web开发、Android:
1. 服务端:
- Spring、Vert.x、Ktor、kotlinx.html等均对Kotlin提供支持
- 可伸缩性:Kotlin 对协程的支持有助于构建服务器端应用程序, 伸缩到适度的硬件要求以应对大量的客户端
协程:一个线程在执行函数时,如果遇到如I/O等阻塞操作,线程可以主动控制,去操作其他函数,等I/O等阻塞操作完成,再回来继续执行原函数。是一种“伪多线程”,无需线程上下文切换的开销,效率高
2. Web开发:
- Kotlin可编译为JavaScript
- 支持与DOM元素交互、支持与图形(如WebGL)交互、支持JQuery和ReactJS等第三方库和框架、兼容CommonJS等
3. Android:
- Android官方支持Kotlin
- 举例:Keepsafe的App Lock应用已100%转换为Kotlin,源代码行数减少
30%
,方法数减少10%
三. Kotlin VS Java
相比Java,Kotlin的优点:
1. 代码简洁
经典例子:
以上针对Person类,Java和Kotlin的实现一致,但Kotlin代码行数减少24行。
Kotlin默认帮类的成员变量设置了getter、setter方法,实现是:
var id: String? = null
set(value : String?) {
id = value // field = value
}
// 以上代码有一个bug,你发现了吗?如果使用id=value,会造成set函数的无限递归调用,最终导致stackoverflow,在Kotlin中默认setter方法中需使用field代替类成员变量,避免set的默认调用
另外简洁性,可参考对比示例:from-java-to-kotlin
2. 安全性强:改善空指针问题
- Top 5 Crashes on Android,Top 1 NullPointerException
- 图灵奖得主Tony Hoare在1965年设计ALGO W编程语言时引入Null引用,2009年,QCon大会上Tony称这是"
十亿美元的错误
"
由上可知空指针问题的严重性,而Kotlin天然的创造性提出可空
与非空类型
,代码阶段就很好减少空指针问题出现的可能性
3. 互操作性:充分利用JVM、Android和浏览器现有库
与Java代码完全可互相操作,.java文件、.kt文件都是编译为.class文件,不过编译过程还是有区别,编译前端(词法分析/语法分析/语义分析/中间代码生成)与Java基本一致,编译后端有区别(做了很多代码封装工作,例如自动生成Getter/Setter方法等,将代码层的很多封装工作转移到编译后端,这也是Kotlin语言简洁的原因)
4. 工具友好:IntelliJ IDEA/Adroid Studio/Eclipse等支持
最后,从语法上举几个明显的差异例子:
- Kotlin创建对象无需
new
关键词 - Kotlin每行代码后无需加"
;
"分号 - Kotlin
主动推断
变量类型(var/val),无需特别声明 - Kotlin的文件类型是
*.kt
- Kotlin方法定义要加关键词
fun
更多与Java的语言特性比较:参考 http://shouce.jb51.net/kotlin/txt/comparison-to-java.html
四. 语法糖
1. 变量
1.1 Kotlin是一种静态类型的语言,编译器根据所赋值的类型来推断类型,类型在编译时解析确定且从不改变。声明变量的2个关键字:var、val
var 变量值可以更改
val 变量值赋值后不能更改
注意:
var a // 局部变量编译错误:The variable must either have a type annotation or be initialized
改为:
var a : String //正确
var a = "test" //正确
1.2 Kotlin中类型是默认的非空值,如果变量是一个可空类型,声明时需要添加"?"
,例如:
var a : String? = null // 正确
var a : String = null // 编译错误
备注:某种程度上,你可以认为非空String 和 可空String是两个不同类型
1.3 如果变量是可空类型,调用时必须增加"?"空检查
,例如:
println (a?.length) // 正确,等于: if (a != null) println(a.length);
println (a.length) // 编译错误
1.4 双叹号!!表示在对象不为空的情况下执行,如果对象为空,则执行时抛出NullPointerException
,例如:
val a : String ?= null
a!!.length;
1.5 类的成员变量需声明时初始化,或声明abstract,或者init函数中初始化,或者使用lateinit var
延迟初始化。另外延迟初始化还可以使用by lazy
,区别是:lateinit var仅支持类成员变量,要求变量是var;by lazy支持类成员变量/局部变量,要求变量是val
private var a : String? // 编译错误
private lateinit var a : String? // 正确
private abstract var a : String? // 正确
private var a : String?
init { a = null } // 正确
备注1:这里也是与Java语法区别,Java中类成员变量可不初始化,有默认值
备注2:Kotlin提供了isInitialized函数来判断变量是否已初始化/赋值
1.6 类的成员变量有这4个可见性修饰符:private
、 protected
、 internal
和public
。 如果没有显式指定修饰符的话,默认可见性是 public。备注:private < protected < internal < public,其中internal指类声明的本模块内可见:
模块是指编译在一起的1套 Kotlin 文件,可以是:
- 1个 IntelliJ IDEA 模块
- 1个 Maven 项目
- 1个 Gradle 源集(例外是 test 源集可以访问 main 的 internal 声明)
- 1次 <kotlinc> Ant 任务执行所编译的一套文件
2.条件语句支持if-else、when。when表达式的每一个分支由一个条件、一个箭头(→)和一个结果来表示
3. 标准函数:let、with、run、apply、also
let函数:
object.let{
it.todo() // 函数内it代替object,可访问其属性和方法
}
使用场景:
场景一:最常用的场景就是使用let函数处理需要针对一个可null的对象统一做判空处理
object?.fun1()
object?.fun2()
使用let函数后:object?.let {
it.fun1()
it.fun2() }
场景二:然后就是需要去明确一个变量处特定的作用域范围内可以使用
with、run、apply、also等函数:
with(object) { // object作为函数参数,函数块内可使用this代替object,返回值为最后一行或return指定
// todo
}
object.run{ // run相当于let和with结合体
// todo
}
object.apply{ // apply和run很像,区别是返回值不一样,apply函数返回传入的对象本身
// todo
}
object.also{ // also和let很像,区别是返回值不一样,also函数返回传入的对象本身
// todo
}
4. 伴生对象 companion object,与类绑定的一个单例对象(编译后.class中转换成static对象),例如:
class MyClass {
companion object {
val logger = LoggerFactory.getLogger(MyClass::class.java)
}
}
5. 一些注解:
5.1 @JvmOverloads
:在有默认参数值的方法中使用此注解,此方法会暴露多个方法
@JvmOverloads fun f(a: String, b: Int=0){ }
相当于:
fun f(a: String){
b = 0;
// ...
}
fun f(a: String, b: Int){}
五. Google建议使用Kotlin最佳实践
- 侧重于可读性,而不是尽量缩短代码行。用 Kotlin 语法糖很容易过度。
- 确立最适合自己团队的编码规范和惯用语。
六. 参考
- from-java-to-kotlin
- Android Kotlin官网
- FQA - Kotlin语言中文站
- Stack Overflow Developer Survey 2019
- Kotlin语言中文站
- Kotlin 势必取代 Java?
- Kotlin 编译之路 "Kotlin编译器"
作者:kevin song,2020.6.22于南京建邺区