2017 年 Google 宣布 Android 为 Kotlin 提供最佳支持并且取代了 Java 的位置,Kotlin 逐渐步入大众的视野。Kotlin 项目最早可以追溯到 2010 年,JetBrains 团队准备开发一款现代化、更强大、更易用的语言,经过五年多的开发,JetBrains 终于在 2016 年年初发布了 Kotlin v1.0 版本。
至今已经过了三年多,Android 最新的官方 Demo、文档等都逐步使用 Kotlin 实现,作为 Android 开发者又怎么能被时代抛弃呢。
现在开始学 Kotlin 的一般都是有 Java 基础的,所以本文也默认读者具备一定的 Java 语言基础。Kotlin 语法跟 Java 谜之相似,毕竟同属于 Java 方言之一,并且更简洁一点,所以很多概念就不展开详细叙述了,只是把规则罗列出来。
变量/属性
- 数组就是类,和 Java 不同,Kotlin 没有声明数组类型的特殊语法
- 如果变量没有初始化器,需要显示的指定它的类型
- val(来自 value)——不可变引用
- var(来自 variable)——可变变量
- val 变量只能进行唯一一次初始化,但是,如果编译期能确保只有唯一一条初始化语句被执行,可以根据条件使用不同的值来初始化它
- 属性可以放到文件的顶层
- 声明了属性的时候就声明了对应的访问器(只读属性有一个 getter,而可写属性既有 getter 又有 setter)
- 也可以自定义访问器,自定义方式如下:
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() = height == width
}
- 默认情况下,顶层属性和其他任意属性一样,是通过访问器暴露给 Java 使用的(如果是 val 就只有一个 getter,如果是 var 就对应一对 getter 和 setter)。
- 如果想把一个常量以 public static final 修饰,可以用 const 来修饰它。
- 扩展属性类比扩展函数
- 扩展属性也像普通成员属性一样,必须定义 getter 函数,因为没有支持字段,因此没有默认 getter 的实现。同理,初始化也不可以,因为没有地方存储值:
var StringBuilder.lastChar: Char
get() = get(length - 1)
set(value: Char){
this.setCharAt(length - 1, value)
}
函数
- 函数可以定义在文件的最外层,不需要把它放在类中
- 关键字 fun 声明一个函数
- 参数的类型写在它名字的后面
- 返回类型放在参数列表之后
- 当调用一个 Kotlin 定义的函数时,可以显示的标明一些参数的名称,如果在调用一个函数时,指明了一个参数的名称,为了避免混淆,那它之后的所有参数都需要标明名称
- 声明函数的时候,指定参数的默认值,这样就可以避免创建重载函数
- 当使用常规的调用语法时,必须按照函数声明中定义的参数顺序来给定参数,可以省略的只有排在末尾的参数,如果使用命名参数,可以省略中间的一些参数,也可以以你想要的任意顺序只给定你需要的参数。
- Java 调用 Kotlin 函数的时候,必须显示地指定所有参数值,如果需要从 Java 代码中频繁调用,而且希望对 Java 使用者更简便,可以用 @JvmOverloads 注解它,这个指示编译期生成 Java 重载函数,从最后一个开始省略每个参数。
- 可以把函数直接放到代码文件的顶层,不用从属于任何类
- Kotlin 编译生成的类的名称,对应于包含函数的文件名称,这个文件中的所有顶层函数编译为这个类的静态函数。
- 扩展函数就是一个类的成员函数,不过定义在类的外面
- 把要扩展的类或接口名称放到即将添加的函数前,这个类的名称被称为接受者类型,用来调用这个扩展函数的那个对象,叫做接受者对象。
fun String.lastChar(): Char = this.get(this.length - 1)
- 其中的 this 可以省略:
fun String.lastChar(): Char = get(length - 1)
- 对于定义个扩展函数,它并不会自动的在整个项目范围内生效,需要进行导入。
- 可以使用关键字 as 来修改导入的类或者函数名称:
import strings.lastChar as last
- 实质上,扩展函数是静态函数,它把对象作为了它的第一个参数。
- Java 调用扩展函数非常简单,调用这个静态函数,然后把接受者对象作为第一个参数传进去既可。
- 这个扩展函数被称为顶层函数,会编译成一个静态函数。
- 扩展函数的静态性质也决定了不能被重写
- 如果一个类的成员函数和扩展函数有相同的签名,成员函数往往会被优先使用。
- 使用 vararg 声明一个函数将有可能有任意多个数量的参数
- 展开运算符 * 用于将任意长度的参数数组解包:
fun main(args: Array<String>){
val list = listOf("args:", *args)
println(list)
}
- 使用 infix 修饰符标记函数,可使其支持中缀符号调用
infix fun Any.to(other: Any) = Pair(this, other)
val (number, name) = 1 to "one"
- 上面的 (number,name) 为解构声明
- 局部函数:
fun saveUser(user: User){
fun validate(value: String, fieldName: String){
if(value.isEmpty()){
throw IllegalArgumentException("Can not save user ${user.id}: empty $fieldName")
}
}
validate(user.name,"Name")
validate(user.address,"Address")
//保存 user 到数据库
}
- 局部函数内部可直接访问外部函数的参数
- 扩展函数也可以被声明为局部函数,但不建议使用:
fun saveUser(user: User){
fun User.validate(value: String, fieldName: String){
if(value.isEmpty()){
throw IllegalArgumentException("Can not save user ${user.id}: empty $fieldName")
}
}
user.validate(user.name,"Name")
user.validate(user.address,"Address")
//保存 user 到数据库
}
类
- 只有数据没有其他代码的称为值对象
- public 是默认属性
- 不区分导入的是类还是函数,而且,它允许使用 import 关键字导入任何种类的生命,例如顶层函数名称
- 可以把多个类放在同一个文件中,文件的名字还可以随意选择,Kotlin 没有对磁盘上源文件的布局加强任何限制
- enum 是一个软关键字:只有当他出现在 class 前才有意义,在其它地方可以把它当做普通的名称来使用。
- 可以给枚举类声明属性和方法
- 声明类的时候,使用一个冒号(:)后面跟上接口的名称,来标记这个类实现了这个接口
- 要改变包含 Kotlin 顶层函数的生成的类的名称,需要为这个文件添加 @JvmName 的注解,将其放入文件开头,位于包名的前面
//使用 data 修饰符表示值对象,默认为 public
data class Person(val name: String, val age: Int)
//使用 enum 创建枚举类
enum class Direction {
EAST,
SOUTH,
WEST,
NORTH
}
//定义 User 接口
interface User
//使用冒号实现/继承接口
class Student(val name: String, val age: Int, val number: Int) : User
参考文献
[1]Kotlin 实战(Kotlin in Action).北京:电子工业出版社,2017.
如果觉得还不错的话,欢迎关注我的个人公众号,我会不定期发一些干货文章~