Kotlin 语法

1,155 阅读9分钟

参考自 Kotlin 中文站

Kotlin 语法

在 Kotlin 中不必使用 ; 来作为代码结束的结束符。

包声明

在 kotlin 中,使用 package 关键字来定义包名。导包则使用 import 关键字。

包名跟 .kt 文件所在的目录可以不匹配,而且 .kt 文件可以随便放到哪个地方。

如上图中,SyntaxSample 类的全称是 me.abc.sample.SyntaxSample

如果没有声明包名则默认为 default 包

默认导入

默认每个 kotlin 文件都会导入一些默认的包,虽然我们看不见,但是它是是在存在的。

下面的包为默认导入的包,其中 * 代表该包下的所有文件:

  • kotlin.*
  • kotlin.*
  • kotlin.annotation.*
  • kotlin.collections.*
  • kotlin.comparisons.*
  • kotlin.io.*
  • kotlin.ranges.*
  • kotlin.sequences.*
  • kotlin.text.*

变量声明

变量使用 var 关键字声明。在 Kotlin 中,变量没有默认初始值,直接使用 var 声明的变量都是非空变量,在引用前必须指定初始值,且初始值不能为 null

var 变量名:变量类型 = 变量初始值

var name:String = "Ricky"

在 Kotlin 里编译器支持自动类型推断,Kotlin 会根据赋值,来推断变量的类型,所以变量的类型不是必须的。当不指定类型时, var 变量可以指定初始值为 null

如果在变量类型后面加个 可空操作符 ,那么这个变量就会转换成可空变量,这时就可以给变量赋值为 null 。但是在使用这个变量时,并不能直接使用。Kotlin 不允许直接使用 可空变量 ,需要进行判空,或者使用 安全调用操作符 ?. ,否则会 报 Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String 错误。这是 Kotlin 的一个重要的特点:变量空安全 。

var name: String? = null
println(name.length) // 这里会报错 
// 为了避免上面的错误,可以进行判空
if (name != null) {
	println(name.length)
}
// 或者使用 安全调用操作符 ?.
println(name?.length)

有时候我们声明的变量,没有办法在声明的第一时间给它赋值,那么我们还可以使用 延迟初始化修饰符 lateinit

// 使用 lateinit 无需对变量在声明时进行初始化,可以在需要的时候进行初始化
lateinit var fileName: String

fun main() {
    fileName = "/opt/home"
}

只读变量

使用 val关键字声明的变量,通常只能赋值一次,不能修改。直接使用 val 声明的变量都是非空的。类似Java中的final修饰的常量。

val 常量名:常量类型 = 常量初始值

val name:String = "Ricky"

在 Kotlin 里编译器支持自动类型判断,val 变量的类型不是必须的,当不指定类型时, val 变量可以指定初始值为 null

虽然 val 修饰的变量是不能被二次赋值,但是可以通过自定义变量的 getter 来让变量的实际取值是动态的。这个不是常规操作。

常量

在 Kotlin 使用 const 关键字修饰,并且其类型只能是 基本类型或者 String ;不能是可以自定义 getter 方法的类型。

const val NAME = "Ricky"

使用 const 修饰的常量叫做编译期常量,是在编译器在编译的时候就知道这个变量在实际运行时的具体值。

使用 const 声明常量需要注意以下问题:

  1. 该常量必须位于顶层或者是 objectcompanion object 的一个成员
  2. 必须是基本类型值或者String
  3. 没有自定义的 getter

比如我们自己创建了一个类 Person ,我们就不能通过 const 来声明 Person 的常量,因为 Person 是可以自定一个 getter 方法的 。

函数

kotlin 中使用 fun 关键字声明函。比如下面的函数计算两数的和:

fun add(a: Int, b: Int): Int {
    return a + b
}

上面的代码表示两个 Int 值相加,并且返回一个 Int 值。

声明完毕后,我们可以像如下方式去调用函数:

val sum = add(2,4)

我们也可以把表达式作为函数体、返回值类型,调用形式不变:

fun add(a: Int, b: Int) = a + b

如果我们不想返回内容,可以指定 Unit 作为返回类型,或者直接省略。

fun reutunNull(): Unit {
    println("Hello World")
}
// 或者
fun reutunNull() {
    println("Hello World")
}
// 如果函数很简单,可以在后面添加一个 = ,在后面添加方法体
fun reutunNull() = println("Hello World")

函数之间还可以嵌套,比如:

fun choose(a: Int, b: Int): Int? {
    fun add(a: Int, b: Int): Int? {
        return a + b
    }

    return add(a,b)
}

在一个函数的内部在定义一个函数,这种函数叫做 局部函数。

函数可以给参数配置默认值,比如:

fun sayHello(name: String = "World"){
    println("Hello , $name")
}

fun main() {
    sayHello()
    sayHello("Ricky")
}

这样我们在调用的时候,可以选择性传还是不传参数。当我们遇到同名重载函数的时候,可以把大部分的内容卸载同一个函数里。比如:

fun sayHello(name: String = "World"){
    println("Hello , $name")
}

fun sayHello(){
   sayHello("World")
}

注释

Kotlin 支持单行和多行注释。具体参见 编写 Kotlin 代码文档

// 单行注释

/* 多行
	注释 */

/**
 * KDoc文档注释,类似 JavaDoc
 * @param <变量名称>
 * @return <用于函数的返回值>
 * @see <另请参>
 */

字符串模板

Kotlin 支持在字符串中引用变量。可以通过 $ 在字符串中引用某个常量或者变量。如果引用的是一个变量的函数、属性或者一个表达式,可以 ${} 来引用。如果需要在字符串中使用 $ 就需要对其的进行转义 ${'$'},这样就可以在字符串中引用 $

    val name = "Ricky"
    val num = 10
    val rise = 10000
    val welcome = """
        welcome 
        No. $num 
        name: $name , 
        name length:${name.length} , 
        social status:${'$'}$rise """

    println(welcome)

上面的代码输出结果为:

    welcome 
    No. 10 
    name: Ricky , 
    name length:5 , 
    social status:$10000 

在 字符串中使用 表达式:

val range = 1..5
println("3 in range is ${3 in range}")

条件表达式

if 表达式

​ 在 kotlin 中 if 是一个表达式,它会有一个值返回。它可以胜任三元运算符。

​ 传统用法:

    if (2 > 1) {
        println("2 > 1")
    } else if (3 > 2){
        println("3 < 2")
    }else{
        print("3 > 2 > 1")
    }

作为表达式:

val a = 10
val b = 20
val max = if (a > b) a else b
println(max)

if 的分支也可以使代码块,分支中的最后一个表达式最为代码块的值返回:

val a = 10
val b = 20
var temp: Int
val max = if (a > b) {
  	// 给 temp 变量赋值
    temp = a
  	// a 作为该代码块的值返回
    a
} else {
		// 给 temp 变量赋值
    temp = b
		// b 作为该代码块的值返回
    b
}

println(max)
println(temp)

when 表达式

when 表达式类似Java中switch语句。when 可以作为表达式使用,也可以作为语句使用。

    val x = 10
    when (x) {
        1 -> println("x 的值为 1")
        2 -> {
            println("x 的值为 2")
        }
        3,4 -> {
            println("x 的值不是3也不是4")
        }
        else -> {
            println("x 的值不是1,2,3,4")
        }
    }

​ when 语句中的参数会将所有的分支条件进行顺序比较,知道某个分支满足条件。when 语句的每个分支都是一个代码块,如果当前分支只有一行代码,可以省略 {} 。如果多个分支的处理方式相同,可以把多个分支放到一起,以逗号分隔。如果其他的分支都不满足条件时,就会执行 else 分支。

​ when 作为表达式时,必须有 else 分支,除非编译器能够检测出所有的可能已经覆盖到了。像 if 一样,每一个分支可以是一个代码块,它的值是块中最后的表达式的值。

val y = 10
val result:String = when (y) {
    1 -> {
        // 代码块
        println("y 的值为 1")
        "y 的值为 1"
    }
    2 -> {
        println("y 的值为 2")
        "y 的值为 2"
    }
    else -> {
        println("y 的值不是 1 也不是 2")
        "y 的值不是 1 也不是 2"
    }
}

println(result)

​ when 表达式不同 Java中的switch语句,它的分支条件可以不是常量,可是任意的表达式

val x = 10
val z = "10"
when (x) {
    z.toInt() -> println(" x = z")
    else -> {
        println(" x != z")
    }
}

when 表达式的分支也可以是一个区间或者集合的判断。比如某个值是在(in)或者不在(!in)一个区间或者集合中:

val x = 10
when (x) {
    in 1..10 -> {
        println("x 在 1-10的区间内")
    }
    !in 11..20 -> {
        println("x 不在 10-20的区间内")
    }
    else->{
        println("x 值到底是多少?")
    }
}

when 表达式的分支也可以判断一个值(is)是或者不是(!is)某个类型:

var k: Any? = 10
when (k) {
    is String -> {
        println("k 的类型是 String")
    }
    is Int -> {
        println("k 的类型是 Int")
    }
    else -> {
        println("k 的类型是什么?")
    }
}

when 表达式也可以用来取代 if...else if 链。如果不提供参数,所有的分支都是简单的布尔表达式,当分支的的条件为真时就执行该分支:

val j = ""
when{
    j.isBlank() -> println("j is blank ")
    j.isEmpty() -> println("j is empty ")
    else->{
        println("j not blank not empty")
    }
}

在kotlin 1.3 的时候,新加了一种语法,可以在when表达式中声明val 常量,并且这个常量作用域的只能在 when 表达式的整个代码块中。

    fun add(a: Int, b: Int): Int? {
        return a.plus(b)
    }

    when (val i = add(1,1)) {
        1 -> println("i = $i")
        2 -> println("i = $i")
        else -> {
            println("i = $i")
        }
    }
		// i 的作用域已经超出了整个 when 表达式的代码块,该语句会报编译器错误
		println("i = $i")

循环控制

for 循环

for 循环可以对任何提供了迭代器(iterator)的对象进行遍历。它的语法如下:

for (item in collection){
  print(item)
}

如果循环体内只有一行代码,可以省略 {}

区间循环

区间使用 .. 操作符,一般都要使用 in 或者 !in 辅助完成操作。

		// 正序遍历 1-10 之间的数
    for (i in 1..10) {
        print("$i ")
    }

		// 在 1-10 之间,从第一个数起,每隔两个数取一个数(取值时包含第一个数)
		// 输出 1 3 5 7 9
    for (i in 1..10 step 2) {
        print("$i ")
    }

		// 倒序遍历 1-10 之间的数,从第一个数起,每隔两个数取一个数(取值时包含第一个数)
		// 输出 10 8 6 4 2 
    for (i in 10 downTo 1 step 2) {
        print("$i ")
    }
数组/集合循环
val array = intArrayOf(1, 2, 3, 4, 5, 6, 7)
// 将数组换成集合遍历形式不变
// val array = listOf(1, 2, 3, 4, 5, 6, 7)
// 通过下标遍历数组
for (i in array.indices) {
    print("下标为 $i 的元素是 ${array[i]}")
    println()
}

// 通过迭代器遍历数组,这里取到的是数组中的每个元素
for (i in array.iterator()) {
    print("$i ")
	  println()
}

// 通过库函数withIndex()遍历数组
for ((index,value) in array.withIndex()) {
    print("下标为 $index 的元素是 $value")
    println()
}
// 通过 lambda 遍历
array.forEach {
	print("$it ")
}

上面代码是遍历数组的形式,同样的只要把数组换成集合,遍历方式一模一样。

while 与 do...while 循环

while 循环结构:每次只有满足条件时才会进入循环。

while(布尔表达式){
  // 循环体内容
}

do...while 循环结构:先循环一次,然后再去查看是否满足条件。不管如何情况,至少会循环一次。

do{
  // 循环体内容
}while(布尔表达式)

var times = 10
while (times > 0) {
    times--
}

do {
    times--
} while (times > 0)

返回与跳转

kotlin 有三种结构化跳转表达式:

  • return:默认从最直接包围它的函数或匿名函数返回
  • break:终止最直接包围它的循环
  • continue:继续下一次最直接包围它的循环

上面三种表达式都可以用作更大表达式的一部分,这些表达式的类型是 Nothing 类型,该类型没有值,而是用于标记永远不能到达的代码位置。

val s = person.name ?: return

上面代码中,如果 person.name 如果为空,就会把 return 表达式的值赋给 ss 的类型就是 Nothing 类型。

break 与 continue 标签

在 kotlin 中任何表达式都可以用标签(label)来标记。标签的格式为标识符后跟 @ 符号,例如 abc@ ,loop@ 都是有效的标签。要给一个表达式加标签,只要在其前加上标签即可。标签类似给一个表达式起一个别名,在别的地方可以引用。

如下:

loop@ for (i in 1..10) {
    // 循环体
}

使用标签限制 break :

loop@ for (i in 1..10) {
    if (i == 3) {
        break@loop
    }
    println(i)
}

上面的代码给 for 循环添加了一个loop 标签,在 i == 3 的时候,结束掉 loop 标签代表的 for 循环 。

上面的例子并没有体现出什么标签的优势,其中的标签可有可无。但是如果遇到嵌套循环就有用很多。

abc@ for (i in 1..5) {
    println("---i = $i----")
    for (j in 1..5) {
        if (i == 3) {
            break@abc
        }
        println("i = $i , j = $j")
    }
}

上面的代码中,有两层 for 循环,我们给外层 for 循环添加了一个名为abc 的标签,然后内层 for 循环在 i == 3 时结束外层标签为 abc 的 for 循环。上面代码的输出结果为:

---i = 1----
i = 1 , j = 1
i = 1 , j = 2
i = 1 , j = 3
i = 1 , j = 4
i = 1 , j = 5
---i = 2----
i = 2 , j = 1
i = 2 , j = 2
i = 2 , j = 3
i = 2 , j = 4
i = 2 , j = 5
---i = 3----

如果将上面的 break 替换成 continue ,就在 i == 3 的时候,跳出当次的外层循环,然后继续下一次循环。输出结果如下:

---i = 1----
i = 1 , j = 1
i = 1 , j = 2
i = 1 , j = 3
i = 1 , j = 4
i = 1 , j = 5
---i = 2----
i = 2 , j = 1
i = 2 , j = 2
i = 2 , j = 3
i = 2 , j = 4
i = 2 , j = 5
---i = 3----
---i = 4----
i = 4 , j = 1
i = 4 , j = 2
i = 4 , j = 3
i = 4 , j = 4
i = 4 , j = 5
---i = 5----
i = 5 , j = 1
i = 5 , j = 2
i = 5 , j = 3
i = 5 , j = 4
i = 5 , j = 5

return 标签

return标签一般都在函数中使用,下面以 数组的 lambda 的表达式为例:

val intArr = intArrayOf(1,2,3,4,5)
intArr.forEach foreach@{
    if (it == 3){
        return@foreach
    }
    println("$it ")
}

上面的代码在 it == 3 会返回然后重新开始下一次遍历。输出结果为

1 2 4 5

kotlin 的标签用来控制breakcontinuereturn 的跳转行为。

总结

  1. 在 Kotlin 中不必使用 ; 来作为代码结束的结束符;
  2. 在 kotlin 中,使用 package 关键字来定义包名。导包则使用 import 关键字;
  3. 包名跟 .kt 文件所在的目录可以不匹配
  4. 默认每个 kotlin 文件都会导入一些默认的包,虽然我们看不见,但是它是是在存在的。
  5. 在 Kotlin 中,变量没有默认初始值;
  6. 使用 var/val 声明的变量都是非空变量,var/val 变量名:变量类型 = 变量初始值
  7. 函数使用关键字 fun 声明 ,返回类型放到方法体后加在 : 后面;
  8. 表达式可以作为函数体、返回值类型 fun add(a: Int, b: Int) = a + b;
  9. 函数
  10. 可以通过 $ 在字符串中引用某个常量或者变量;需要在字符串中使用 $ 就需要对其的进行转义 ${'$'}
  11. 支持 单行注释 ,多行注释 ,注释嵌套;
  12. if else 可以作为表达式,分支中的最后一个表达式最为代码块的值;可以当做三元运算符;
  13. when 可以作为表达式使用,也可以作为语句使用;when 表达式也可以用来取代 if...else if 链。如果不提供参数,所有的分支都是简单的布尔表达式
  14. 支持for / while / for...while 循环
  15. 支持区间
  16. kotlin 可以使用标签用来控制breakcontinuereturn 的跳转行为。