文章

Kotlin基础——基础语法

开始

kotlin的主程序代码结构如下所示

1
2
3
4
5
6
package com.example.kotlin

import ...

fun main(args: Array<String>) {
}

kotlin代码文件后缀为.kt,kotlin文件不需要声明所处的包,没有指定包时,默认位于default包中

kotlin已经默认导入了一些包,可以直接使用其中的静态函数

kotlin支持多种注释

1
2
3
4
// 单行注释
/*
多行注释
*/

变量

可以使用varval声明一个变量,变量类型接在冒号后面

1
2
var a: Int = 3
val b: Int = 5

var用于声明可变变量,val声明不可变变量

kotlin支持类型推断

1
var a = 3 // a: Int

数据类型

kotlin中没有基本数据类型(primitive type),所有类型都是对象

  • 整数类型:Byte、Short、Int、Long,kotlin不支持八进制

  • 浮点类型:Float、Double,小数字面量默认推断为Double

    kotlin不支持小类型隐式转换为大类型(Float to Double)

    kotlin中的数值数据类型不支持隐式类型转换,需要调用toXXX()方法显式转换

  • 字符:Char,Kotlin中的Char与Int同样不能隐式类型转换

  • 布尔:Boolean

  • 字符串:String,String不可变,重载了[]获取其中的字符

    • 多行字符串:使用三引号'''可以创建多行字符串
    • 字符串模板:在字符串中可以使用$value,在字符串中填充value值,长的表达式用大括号括起来${}

JVM平台的基本类型

对于基本类型,jvm平台类型为primitive-type(int,float,double,char,boolean)

  • 当创建数值类型的可空引用或使用泛型时,kotlin会使用java的包装类来实现这些基本类型的可空类型(自动装箱为包装类)
  • 当创建不可空引用时,kotlin会使用primitive-type

数组

kotlin中的数组都是Array类型,通过泛型指定类型,对于primitive-type,kotlin提供了相应的Array(IntArray、FloatArray等),可以省去装箱开销。Array类型不支持协变

kotlin中的数组自带一个size属性,以及重载了[]为getter和setter

创建数组方法

  • arrayOf方法:传入可变参数
  • Array构造函数:传入数组大小和一个初始化函数闭包

区间

操作符和函数

  • in判断某个值是否在指定区间内,!in为非逻辑

  • ..操作符:定义一个正向区间,a..b表示[a,b]

  • until中缀函数:定义一个正向半开区间,a util b表示[a,b)

  • downTo中缀函数:定义一个反向区间,a downTo b表示[a,b]逆序

  • step中缀函数:由区间对象调用,设置区间对象的步进大小,返回一个XXXProgression对象

    区间对象不存储步进信息,只表示范围,所以调用后会使用区间创建一个XXXProgression对象并设置步进

区间本质是一个ClosedRange<T>对象(对于原始类型,对应IntRange等类型,避免装箱),其中包含一些操作方法

区间操作符支持所有可比较类型,内部使用了比较运算符进行比较,比较运算符会转换为compareTo方法,因此对于自定义类型,需要实现Comparable接口。对于自定义类型,只支持..操作符

当区间对象用于遍历时,使用的是区间对象的iterator方法获取一个迭代器,自定义类型区间对象没有iterator方法,因此需要自己实现自定义类型的迭代器

比较运算符

与java比较运算符类似,由于kotlin的类型都是对象,因此比较都是基于compareTo方法,当使用比较运算符时会自动转换为compareTo方法,对于自定义类型,可以实现Comparable接口

相等运算符

  • ==:比较对象的值
  • ===:比较对象的地址

NULL安全检查

kotlin中的对象总体分为可空对象和不可空对象,默认为不可空对象,若要使用可空对象,需要在类型后添加?

1
2
val a: Int = 20
val b: Int? = null

可空调用:对可空对象调用其方法和属性时需要显式声明

1
2
var str: String?
var length: Int? = str?.length // 当str为空时不调用返回null,length为可空对象

强制为不可空

1
var len: Int = str!!.length // 当str为空时会抛出NPE

空判断赋值

1
var len: Int = str?.length ?: -1 // 当?:前不为null时取前面的值,否则取后面的值

在与Java互操作的实践中,私以为还是把从Java传到kotlin的对象全部看做可空类型比较好,因为无法保证Java传入的对象一定是非空的。要注意Java传入对象时,IDE可能会提示类型是非空的,这时还是需要按可空来处理(好一点的时候会提示为平台类型)

类型转换与类型检查

is运算符与空判断

使用is运算符可以检查某个对象是否为某个类型

在进行空判断后,对象会自动转换为不可空类型和可空类型

1
2
3
4
5
6
7
8
9
10
var obj: Any
if (obj is String) {
    // 使用is运算符后,在该块中,obj会自动转换为String
}
// 在块外部,obj依然是Any类型
var obj1: String?
if (obj1 == null) {
    // 进行空判断后,obj1在该块中为可空类型
}
// 在块外会自动转换为不可空类型

转换操作符as

若两个类型具有继承关系时,子类对象可以直接赋值给父类引用,父类引用通过as关键字转换为子类引用

1
2
val person: Person = Student()
val student: Student = person as Student

直接使用as操作符是不安全的,当person为可空类型并且值为null时,转换会失败,并且抛出NPE

1
2
3
val person: Person? = Student()
val student: Student? = person as Student?  // 转换为可空类型
val student1: Student? = person as? Student  // 使用安全的转换操作符

as操作符可以在导入同名类时,将其中一个类进行重命名,这个用到的地方不多

可空与不可空原理

kotlin的可空与不可空检查发生在编译时和运行时

kotlin允许不可空对象赋值给可空引用,不允许可空对象赋值为不可空引用

1
2
3
4
5
6
var a: Int = 10
var b: Int? = a  // 编译通过

var a: Int? = 10
var b: Int = a  // 编译错误
var b: Int = a ?: -1  // 空判断赋值

当将一个不可空对象赋值给一个可空引用时,会将不可空对象的引用浅拷贝到可空引用,不会创建新的对象,这两个引用指向同一个对象(a === b = true)

流程控制

kotlin中几乎所有语句都可以作为表达式

if语句

if语句支持表达式,在每个分支中的最后一个表达式作为该分支的返回值

当if作为表达式时,else分支必须存在

1
2
3
4
5
6
7
8
val x = if (condition) value1 else value2
val y = if (condition) {
    println(...)
    value1
} else {
    println(...)
    value2
}

when语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 与switch类似
when(variable) {
    value1 -> {
        // statement
    }
}

// when语句可以作为表达式
val x = when(variable) {
    value1 -> {
        // statement
        // expression
    }
    value2 -> {
        // statement
        // expression
    }
    // 当when作为表达式,variable没有全部枚举时,必须添加else
    else -> {
        // statement
        // expression
    }
}

// 多值匹配、表达式匹配、区间匹配、类型匹配
when(variable) {
    value1, value2 -> {
        // statement
    }
    // 表达式的返回值必须与variable类型相同
    expression -> {
        // statement
    }
    in start..end -> {
        // statement
    }
    // 在该块中variable被自动转换为String
    is String -> {
        // statement
    }
}

// if-else形式
// 此时when语句没有参数
when {
    // condition为布尔表达式
    condition -> {
        // statement
    }
    else -> {
        // 必须有else语句
    }
}

// 引入临时变量
when (val v = expression) {
    // 变量v只用于when块中
    // ...
}

for语句

kotlin的for循环为for-in,支持所有提供了Iterator的对象的遍历

1
2
3
4
5
6
7
for (item in collection) {
    // use item
}
// 数字区间遍历,i为隐式名称
for (i in start..end) {
    // statement
}

通过数组索引遍历集合

1
2
3
4
for (i in arr.indices) {
   	// statement
}
for ((index, item) in arr.withIndex())

控制关键字

break和continue与java中的作用相同

kotlin的任何表达式都可以设置一个标签,格式为label@,可以引用标签进行流程控制,引用格式为@label

每个函数拥有一个同名的隐式标签,通常使用隐式标签,任何表达式都可以设置一个标签,但不一定能够引用它,没有设置标签时,通过隐式标签引用

标签通常用于return、continue、break,但不是所有表达式都具有作用域并且能够返回,因此不是所有表达式的标签都能用于return、continue、break

break标签、continue标签

1
2
3
4
5
6
loop@ for (i in 1..100) {
    for (j in 1..100) {
        break@loop
        // continue@loop
    }
}

return标签

return默认从最内层的函数作用域和匿名函数中返回,使用标签可以返回到外部的作用域

  • 普通函数/匿名函数:任何函数都可以使用标签进行返回return@label value,但不是所有函数都能设置自定义标签,只有能够作为表达式的函数才能设置标签,通常直接使用return

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    // 顶层函数,不是表达式
    fun f() {
        return@f
    }
    // 成员函数,不是表达式
    class Person {
        fun say() {
            return@say
        }
    }
    // 局部函数,可以设置标签
    fun outer() {
        inter@ fun inter() {
            return@inter
        }
    }
    // 匿名函数,可以设置标签
    val f: () -> Unit = unnamed@ fun () {
        return@unnamed
    }
    
  • lambda表达式:lambda表达式不支持使用return从lambda中返回,因此要实现从lambda中返回需要使用标签

    1
    2
    3
    
    val f: () -> Unit = f@ {
        return@f  // 不使用标签直接return结束f所在的作用域无法实现,已经不允许直接使用return
    }
    

异常

kotlin的所有异常类都是Throwable的子类,kotlin中同样有try-catch-finally,作用与java相同,kotlin的所有异常都是运行时异常

try可作为表达式,表达式的类型为Nothing

1
2
3
4
5
6
7
8
9
val x = try {
    // statement
    // expression
} catch(Throwable e) {
    // statement
    // expression
} finally {
    // finally中不支持表达式返回值
}

Nothing类型通常表示一个函数永远不会返回或者一个集合为空

1
2
3
4
5
// 直接抛出异常,函数永远没有返回值
fun method(): Nothing {
    throw ...
}
var list: List<Nothing> = listOf()
本文由作者按照 CC BY 4.0 进行授权