Kotlin: 语法篇
Admin Lv3

作为安卓开发的现任官方语言,Kotlin的名字取自于圣彼得堡旁边的一座小岛,就像其前任Java的名字取自于印度尼沙的一座小岛一样,暗示着自己准备将其取而代之的野心

与Java的互操作性

Java的势力广大,囊括了很多主流的安卓App和Library,想要诱使Java开发者们改宗必须做出一些妥协: Kotlin和Java代码可以互相调用,兼容已有的遗产代码; Kotlin可以让自己被编译成Java字节码,进而被JVM执行,实现与Java共享运行环境

类型系统

不像Java分primitive和reference类型,Kotlin贯彻着万物皆为对象的原则,将Any/Any?作为根类型被其他类型继承。除此之外,为了保持null安全性,Kotlin的类型分为可空类和非空类,一一对应

1
2
3
4
val s1: String // 非空类型声明
s1 = null // 编译报错
val s2: String? // 可空类型声明
s3 = null // 成功

访问可空表达式的属性和方法时需要在表达式后面加个问号

1
2
3
val x: Int? = listOf(4444, null).random()
println(x.toHexString()) // 编译报错
println(x?.toHexString()) // 成功

使用埃尔维斯运算符?:给可空的表达式加一个备用值

1
println(listOf(4444, null).random() ?: 5555)

当然保留了Java的三元运算符,不过换了一种形式

1
println( if ( listOf(4444, null).random() ) 5555 else 6666)

不同于Java,Kotlin中所有语句都可以写成表达式,给其分配一个值,不产生值的则会被分配一个特殊值Unit,从属于Unit类型,代表没有东西。

1
println(if ( listOf(true, false).random() ) { } else { } ) // 回显kotlin.Unit

函数作为对象也有自己的类型,可以直接被塞到变量里面然后调用

1
2
val f: (Double, Double) -> Double = { x, y -> listOf(x,y).random() }
println(f(1.1, 2.2))

Kotlin还可以根据初始表达式自动推断变量类型,免去声明的麻烦

1
2
3
4
val x = "abc" // String
val y = 1L // Long
val z = { x: Int, y: String -> listOf(x,y).random() } // (Int, String) -> Any
val w = listOf(1, 2.0, "A") // List<Any>

变量和函数

变量和函数不必像Java一样必须在class里定义

1
2
3
4
5
6
7
8
9
10
fun main() {
val x = 1 // 只读变量,类似于final
x = 2 // 报错
var y = 1 // 可写变量
y = 2 // 成功
}

fun foo(x: Int, action: (Int) -> Int) : Int {
return action(x)
}

传参的时候如果最后一个参数是函数可以用Block语法写在外面

1
foo(2) { x -> x * x }

扩展函数可以给已有的class扩展方法

1
2
3
fun File.getWordlist() {
return this.readText().split(" ").distinct()
}

自定义类

在class里val/var语句被用作定义属性,属性由field,initializer,getter以及setter组成,当然只读属性没有setter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class C {
var x = 233
set(newValue) {
field = newValue * 2
}
get() {
return field
}
val y = 666
set(newValue) { field = newValue } // 编译报错
}
fun main() {
val c = C()
println(c.x) // 233
c.x = 233
println(c.x) // 466
}

如果getter和setter都只需要默认的,可以直接将属性写进原初构造函数里

1
2
3
4
5
6
class C(val x: Int = 233) {}

fun main() {
println(C().x) // 233
println(C(666).x) // 666
}

如果想在构造时做一些额外的事,可以使用init块以及次级构造函数,值得注意的是每个次级构造函数需要先调用原初构造函数

1
2
3
4
5
6
7
8
9
10
11
class C(var x: Int) {
init {
x *= 10
}
constructor(initX, y) : this(initX) {
x += y
}
}
fun main() {
println(C(233, 3).x) // 2333
}

执行的顺序是原初构造函数->每个属性的initializer->init块->次级构造函数
如果在声明一个属性的时候还没想好初始值,可以使用lateinit关键字告诉编译器”不用管这个反正我在用之前会初始化的”

1
2
3
class C {
lateinit var x
}

在优化性能时如果想要把只读属性的初始化操作分散到不同的时间点中,可以使用懒初始化声明,让属性在被真正用到时再初始化

1
2
3
class C {
val x by lazy { 233 }
}

用伴生对象实现和类绑定的静态属性和方法

1
2
3
4
5
6
7
8
9
10
class C {
companion object {
val X = listOf(233, 666).random()
const val Y = "AAA" // 如果常数值在编译时已知,可以用const标记免除初始化操作
fun foo(): Int { return 233 }
}
}
fun main() {
println(C.X) // 233/666
}

对于存放数据的类型,Kotlin贴心的提供了数据类,默认有着equals,hashCode,toString符合直觉的方法实现以及构造函数

1
2
3
4
5
data class C(var x: Int, var y: Int) { }
fun main() {
val c = C(2, 4)
println(c) // C(x=2, y=4)
}

最后是有着自定义固定值的Enum类

1
2
3
4
5
6
7
8
9
10
11
12
13
enum class E {
A, B, C, D
}
fun main() {
val x = listOf(E.A, E.B, E.C, E.D).random()
when (x) {
E.A -> println("A")
E.B -> println("B")
E.C -> println("C")
E.D -> println("D")
else -> println("Should not get there!")
}
}

可见性修饰符

可见性修饰符可以用来限制变量,函数,类,以及方法的使用范围,不标记默认被当作public

  • public: 无限制
  • internal: 只有同一模块可见
  • protected: 当前类以及子类可见
  • private: 对于类中声明,当前类可见,对于顶层声明,当前文件内可见