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
// 单行注释
/*
多行注释
*/
变量
可以使用var
和val
声明一个变量,变量类型接在冒号后面
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
,但不是所有函数都能设置自定义标签,只有能够作为表达式的函数才能设置标签,通常直接使用return1 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()