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

包声明

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

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

image-20190814181633983.png

如上图中,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

  1. var 变量名:变量类型 = 变量初始值
  2. 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 的一个重要的特点:变量空安全

只读变量

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

  1. val 常量名:常量类型 = 常量初始值
  2. val name:String = "Ricky"

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

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

常量

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

  1. const val NAME = "Ricky"

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

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

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

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

函数

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

  1. fun add(a: Int, b: Int): Int {
  2. return a + b
  3. }

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

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

  1. val sum = add(2,4)

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

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

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

  1. fun reutunNull(): Unit {
  2. println("Hello World")
  3. }
  4. 或者
  5. fun reutunNull() {
  6. println("Hello World")
  7. }

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

  1. fun choose(a: Int, b: Int): Int? {
  2. fun add(a: Int, b: Int): Int? {
  3. return a + b
  4. }
  5. return add(a,b)
  6. }

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

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

  1. fun sayHello(name: String = "World"){
  2. println("Hello , $name")
  3. }
  4. fun main() {
  5. sayHello()
  6. sayHello("Ricky")
  7. }

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

  1. fun sayHello(name: String = "World"){
  2. println("Hello , $name")
  3. }
  4. fun sayHello(){
  5. sayHello("World")
  6. }

注释

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

  1. // 单行注释
  2. /* 多行
  3. 注释 */
  4. /**
  5. * KDoc文档注释,类似 JavaDoc
  6. * @param <变量名称>
  7. * @return <用于函数的返回值>
  8. * @see <另请参>
  9. */

字符串模板

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

  1. val name = "Ricky"
  2. val num = 10
  3. val rise = 10000
  4. val welcome = """
  5. welcome
  6. No. $num
  7. name: $name ,
  8. name length:${name.length} ,
  9. social status:${'$'}$rise """
  10. println(welcome)

上面的代码输出结果为:

  1. welcome
  2. No. 10
  3. name: Ricky ,
  4. name length5 ,
  5. social status$10000

在 字符串中使用 表达式:

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

条件表达式

if 表达式

  1. kotlin if 是一个表达式,它会有一个值返回。它可以胜任三元运算符。
  2. 传统用法:
  1. if (2 > 1) {
  2. println("2 > 1")
  3. } else if (3 > 2){
  4. println("3 < 2")
  5. }else{
  6. print("3 > 2 > 1")
  7. }

作为表达式:

  1. val a = 10
  2. val b = 20
  3. val max = if (a > b) a else b
  4. println(max)

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

  1. val a = 10
  2. val b = 20
  3. var temp: Int
  4. val max = if (a > b) {
  5. // 给 temp 变量赋值
  6. temp = a
  7. // a 作为该代码块的值返回
  8. a
  9. } else {
  10. // 给 temp 变量赋值
  11. temp = b
  12. // b 作为该代码块的值返回
  13. b
  14. }
  15. println(max)
  16. println(temp)

when 表达式

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

  1. val x = 10
  2. when (x) {
  3. 1 -> println("x 的值为 1")
  4. 2 -> {
  5. println("x 的值为 2")
  6. }
  7. 3,4 -> {
  8. println("x 的值不是3就是4")
  9. }
  10. else -> {
  11. println("x 的值不是1,2,3,4")
  12. }
  13. }
  1. when 语句中的参数会将所有的分支条件进行顺序比较,知道某个分支满足条件。when 语句的每个分支都是一个代码块,如果当前分支只有一行代码,可以省略 `{}` 。如果多个分支的处理方式相同,可以把多个分支放到一起,以逗号分隔。如果其他的分支都不满足条件时,就会执行 else 分支。
  2. when 作为表达式时,必须有 else 分支,除非编译器能够检测出所有的可能已经覆盖到了。像 if 一样,每一个分支可以是一个代码块,它的值是块中最后的表达式的值。
  1. val y = 10
  2. val result:String = when (y) {
  3. 1 -> {
  4. // 代码块
  5. println("y 的值为 1")
  6. "y 的值为 1"
  7. }
  8. 2 -> {
  9. println("y 的值为 2")
  10. "y 的值为 2"
  11. }
  12. else -> {
  13. println("y 的值不是 1 也不是 2")
  14. "y 的值不是 1 也不是 2"
  15. }
  16. }
  17. println(result)
  1. when 表达式不同 Java中的switch语句,**它的分支条件可以不是常量,可是任意的表达式**。
  1. val x = 10
  2. val z = "10"
  3. when (x) {
  4. z.toInt() -> println(" x = z")
  5. else -> {
  6. println(" x != z")
  7. }
  8. }

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

  1. val x = 10
  2. when (x) {
  3. in 1..10 -> {
  4. println("x 在 1-10的区间内")
  5. }
  6. !in 11..20 -> {
  7. println("x 不在 10-20的区间内")
  8. }
  9. else->{
  10. println("x 值到底是多少?")
  11. }
  12. }

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

  1. var k: Any? = 10
  2. when (k) {
  3. is String -> {
  4. println("k 的类型是 String")
  5. }
  6. is Int -> {
  7. println("k 的类型是 Int")
  8. }
  9. else -> {
  10. println("k 的类型是什么?")
  11. }
  12. }

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

  1. val j = ""
  2. when{
  3. j.isBlank() -> println("j is blank ")
  4. j.isEmpty() -> println("j is empty ")
  5. else->{
  6. println("j not blank not empty")
  7. }
  8. }

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

  1. fun add(a: Int, b: Int): Int? {
  2. return a.plus(b)
  3. }
  4. when (val i = add(1,1)) {
  5. 1 -> println("i = $i")
  6. 2 -> println("i = $i")
  7. else -> {
  8. println("i = $i")
  9. }
  10. }
  11. // i 的作用域已经超出了整个 when 表达式的代码块,该语句会报编译器错误
  12. println("i = $i")

循环控制

for 循环

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

  1. for (item in collection){
  2. print(item)
  3. }

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

区间循环

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

  1. // 正序遍历 1-10 之间的数
  2. for (i in 1..10) {
  3. print("$i ")
  4. }
  5. // 在 1-10 之间,从第一个数起,每隔两个数取一个数(取值时包含第一个数)
  6. // 输出 1 3 5 7 9
  7. for (i in 1..10 step 2) {
  8. print("$i ")
  9. }
  10. // 倒序遍历 1-10 之间的数,从第一个数起,每隔两个数取一个数(取值时包含第一个数)
  11. // 输出 10 8 6 4 2
  12. for (i in 10 downTo 1 step 2) {
  13. print("$i ")
  14. }

数组/集合循环
  1. val array = intArrayOf(1, 2, 3, 4, 5, 6, 7)
  2. // 将数组换成集合遍历形式不变
  3. // val array = listOf(1, 2, 3, 4, 5, 6, 7)
  4. // 通过下标遍历数组
  5. for (i in array.indices) {
  6. print("下标为 $i 的元素是 ${array[i]}")
  7. println()
  8. }
  9. // 通过迭代器遍历数组,这里取到的是数组中的每个元素
  10. for (i in array.iterator()) {
  11. print("$i ")
  12. println()
  13. }
  14. // 通过库函数withIndex()遍历数组
  15. for ((index,value) in array.withIndex()) {
  16. print("下标为 $index 的元素是 $value")
  17. println()
  18. }
  19. // 通过 lambda 遍历
  20. array.forEach {
  21. print("$it ")
  22. }

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

while 与 do…while 循环

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

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

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

  1. do{
  2. // 循环体内容
  3. }while(布尔表达式)
  1. var times = 10
  2. while (times > 0) {
  3. times--
  4. }
  5. do {
  6. times--
  7. } while (times > 0)

返回与跳转

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

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

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

  1. val s = person.name ?: return

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

break 与 continue 标签

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

如下:

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

使用标签限制 break :

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

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

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

  1. abc@ for (i in 1..5) {
  2. println("---i = $i----")
  3. for (j in 1..5) {
  4. if (i == 3) {
  5. break@abc
  6. }
  7. println("i = $i , j = $j")
  8. }
  9. }

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

  1. ---i = 1----
  2. i = 1 , j = 1
  3. i = 1 , j = 2
  4. i = 1 , j = 3
  5. i = 1 , j = 4
  6. i = 1 , j = 5
  7. ---i = 2----
  8. i = 2 , j = 1
  9. i = 2 , j = 2
  10. i = 2 , j = 3
  11. i = 2 , j = 4
  12. i = 2 , j = 5
  13. ---i = 3----

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

  1. ---i = 1----
  2. i = 1 , j = 1
  3. i = 1 , j = 2
  4. i = 1 , j = 3
  5. i = 1 , j = 4
  6. i = 1 , j = 5
  7. ---i = 2----
  8. i = 2 , j = 1
  9. i = 2 , j = 2
  10. i = 2 , j = 3
  11. i = 2 , j = 4
  12. i = 2 , j = 5
  13. ---i = 3----
  14. ---i = 4----
  15. i = 4 , j = 1
  16. i = 4 , j = 2
  17. i = 4 , j = 3
  18. i = 4 , j = 4
  19. i = 4 , j = 5
  20. ---i = 5----
  21. i = 5 , j = 1
  22. i = 5 , j = 2
  23. i = 5 , j = 3
  24. i = 5 , j = 4
  25. i = 5 , j = 5

return 标签

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

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

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

  1. 1 2 4 5

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

总结:

  1. 在 Kotlin 中,所有的东西都是对象,基本类型也是对象;
  2. 基本类型包含:Double、Float、Long、Int、Short、Byte、Char、Boolean,每个类型都是一个类;
  3. 声明变量时,如果不指定变量的类型,Kotlin 会根据当前变量值推断类型;
  4. 声明整形数字时,默认为 Int ;声明浮点型数字时,默认为 Double;
  5. 在 Kotlin 中,变量声明为可空类型时,即使用 ? 操作符修饰时,就会触发装箱的操作;
  6. 每个基本类型都是一个类,各个基本类型不能进行隐式转换;
  7. 每个基本都是提供了方法显式转换成其它的各个基本类型;
  8. Kotlin支持数字运算的标准集
  9. Kotlin支持各种位运算,但是没有特定的符号,需要使用中缀方式调用命名函数;
  10. 字符 Char 类型 不能直接当作数字;
  11. 特殊字符需要使用转义字符 \ 进行转义,支持常见的转义字符;
  12. 数组使用 Array 类表示,它定义了 get/set 函数以及 size 属性;
  13. 以使用库函数 arryOf() 创建一个数组并传递元素值给它。或者使用库函数 arryOfNulls 创建一个指定大小的、所有元素都为空的数组;
  14. 使用库函数 arryOf()/arryOfNulls() 会涉及到拆箱装箱;
  15. 使用原生类型数组可以避免拆箱装箱;
  16. 字符串用 String 类型表示。字符串是不可变的。可以使用 [] 通过下标访问字符串中的某个字符;
  17. 可以使用字符串模板进行字符串连接;也可以使用 + 连接字符串,但不推荐;
  18. 原始字符串使用三个引号(""")分界符括起来,内部没有转义并且可以包含换行以及任何其他字符;并且可以通过 trimMargin()函数去除前导空格;