「Kotlin」 Kotlin基础语法

Posted by Dawn-K's Blog on June 27, 2020

Kotlin基础语法

[toc]

文件结构

Kotlin 的源代码结尾是 .kt

Kotlin 不需要将函数都放到类中, 即有全局变量和暴露在外面的函数, 类似c++, 也是依靠main函数来确定入口.

Kotlin 的语句结尾不需要加分号

注释

kotlin 注释和java cpp基本没有区别

1
2
3
// 这是一个单行注释
/* 这是一个多行的
   块注释。 */

变量

kotlin 拥有自动类型推导系统.

1
2
3
4
5
6
7
8
9
10
11
//  值
//  val <标识符> : <类型> = <初始化值>
val a: Int = 1
val b = 1       // 系统自动推断变量类型为Int
val c: Int      // 如果不在声明时初始化则必须提供变量类型
c = 1           // 明确赋值

// 变量
// var <标识符> : <类型> = <初始化值>
var x = 5        // 系统自动推断变量类型为Int
x += 1           // 变量可修改

循环

数值循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// in 这个操作符除了在for中可以用, 还可以在if中用

// 注意到这里的i也不需要声明类型?

for (i in 1..4) print(i) // 输出“1234”

for (i in 4..1) print(i) // 什么都不输出,必须将...换成downTo

if (i in 1..10) { // 等同于 1 <= i && i <= 10
    println(i)
}

// 使用 step 指定步长
for (i in 1..4 step 2) print(i) // 输出“13”

for (i in 4 downTo 1 step 2) print(i) // 输出“42”

// 使用 until 函数构造左闭右开区间
for (i in 1 until 10) {   // i in [1, 10) 排除了 10
     println(i)
}

repeat

1
2
3
4
// 取代 for(int i=0;i<5;i++)
repeat(5){ i ->
    println("循环运行第${i+1}次。")
}

遍历容器

1
2
3
4
5
6
7
8
9
10
11
12
for (item in collection) print(item)

// 如果你想要通过索引遍历一个数组或者一个 list,你可以这么做:
// indices 是 index 的复数形式
for (i in array.indices) {
    print(array[i])
}

// 或者你可以用库函数 withIndex:
for ((index, value) in array.withIndex()) {
    println("the element at $index is $value")
}

While

1
2
3
4
5
6
7
while( 布尔表达式 ) {
    //循环内容
}

do {
    //代码语句
}while(布尔表达式)

返回和跳转

1
2
3
4
5
6
7
8
// 在循环中 Kotlin 支持传统的 break 和 continue 操作符.
fun main(args: Array<String>) {
    for (i in 1..10) {
        if (i==3) continue  // i 为 3 时跳过当前循环,继续下一次循环
        println(i)
        if (i>5) break   // i 为 6 时 跳出循环
    }
}

以下内容等用到的时候再深入学习

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
// Break 和 Continue 标签
// 在 Kotlin 中任何表达式都可以用标签(label)来标记.
// 标签的格式为标识符后跟 @ 符号, 例如:abc@、fooBar@都是有效的标签.
// 要为一个表达式加标签, 我们只要在其前加标签即可.

// 现在, 我们可以用标签限制 break 或者continue:
loop@ for (i in 1..100) {
    for (j in 1..100) {
        if (……) break@loop
    }
}
// 标签限制的 break 跳转到刚好位于该标签指定的循环后面的执行点. continue 继续标签指定的循环的下一次迭代.

// 标签处返回
// Kotlin 有函数字面量、局部函数和对象表达式. 因此 Kotlin 的函数可以被嵌套.
// 标签处返回最重要的一个用途就是从 lambda 表达式中返回.

// 这个 return 表达式从最直接包围它的函数即 foo 中返回.
fun foo() {
    ints.forEach {
        if (it == 0) return
        print(it)
    }
}

// 如果我们需要从 lambda 表达式中返回, 我们必须给它加标签并用以限制 return.
fun foo() {
    ints.forEach lit@ {
        if (it == 0) return@lit
        print(it)
    }
}

// 通常情况下使用隐式标签更方便. 该标签与接受该 lambda 的函数同名.
fun foo() {
    ints.forEach {
        if (it == 0) return@forEach
        print(it)
    }
}

// 或者, 我们用一个匿名函数替代 lambda 表达式. 匿名函数内部的 return 语句将从该匿名函数自身返回
fun foo() {
    ints.forEach(fun(value: Int) {
        if (value == 0) return
        print(value)
    })
}

//当要返一个回值的时候, 解析器优先选用标签限制的 return, 即
return@a 1
意为"从标签 @a 返回 1", 而不是"返回一个标签标注的表达式 (@a 1)".

数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// [1,2,3]
val a = arrayOf(1, 2, 3)

// 工厂函数,第一个参数是列表长度,第二个参数是生成方法
// [0,2,4]
val b = Array(3, { i -> (i * 2) })

//读取数组内容
println(a[0])    // 输出结果:1
println(b[1])    // 输出结果:2

// 除了类Array,还有ByteArray, ShortArray, IntArray,用法相同
val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]

字符串

普通字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 和 Java 一样,String 是不可变的
// 方括号 [] 语法可以很方便的获取字符串中的某个字符,也可以通过 for 循环来遍历:
for (c in str) {
    println(c)
}

fun main(args: Array<String>) {
    val text = """
    多行字符串
    多行字符串
    """
    println(text)   // 输出有一些前置空格
}

fun main(args: Array<String>) {
    val text = """
    |多行字符串
    |菜鸟教程
    |多行字符串
    |Runoob
    """.trimMargin()
    println(text)    // 前置空格删除了
}
// 默认 | 用作边界前缀,但你可以选择其他字符并作为参数传入,比如 trimMargin(">")。

字符串模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// $ 表示一个变量名或者变量值
// $varName 表示变量值
// ${varName.fun()} 表示变量的方法返回值:

var a = 1
// 模板中的简单名称:
val s1 = "a is $a" 

a = 2
// 模板中的任意表达式:
val s2 = "${s1.replace("is", "was")}, but now is $a"

// 原生字符串和转义字符串内部都支持模板。 如果你需要在原生字符串中表示字面值 $ 字符(它不支持反斜杠转义),你可以用下列语法:
fun main(args: Array<String>) {
    val price = """
    ${'$'}9.99
    """
    println(price)  // 求值结果为 $9.99
}

基本数据类型

Byte、Short、Int、Long、Float、Double

字面量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 你可以使用下划线使数字常量更易读:
// 十进制:123
// 长整型以大写的 L 结尾:123L

// 16 进制以 0x 开头:0x0F
// 2 进制以 0b 开头:0b00001011
// 注意:8进制不支持

// Kotlin 同时也支持传统符号表示的浮点数值:
// Doubles 默认写法: 123.5, 123.5e10
// Floats 使用 f 或者 F 后缀:123.5f

// 可以使用下划线使数字常量更易读
val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010

比较

你每定义的一个变量, 其实 Kotlin 帮你封装了一个对象, 这样可以保证不会出现空指针. 数字类型也一样, 所以在比较两个数字的时候, 就有比较数据大小和比较两个对象是否相同的区别了. 三个等号 === 表示比较对象地址, 两个 == 表示比较两个值大小.

类型转换

由于不同的表示方式, 较小类型并不是较大类型的子类型, 较小的类型不能隐式转换为较大的类型. 这意味着在不进行显式转换的情况下我们不能把 Byte 型值赋给一个 Int 变量.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
val b: Byte = 1 // OK, 字面值是静态检测的
val i: Int = b // 错误
// 我们可以代用其toInt()方法.

val b: Byte = 1 // OK, 字面值是静态检测的
val i: Int = b.toInt() // OK
// 每种数据类型都有下面的这些方法, 可以转化为其它的类型:

toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char

有些情况下也是可以使用自动类型转化的, 前提是可以根据上下文环境推断出正确的数据类型而且数学操作符会做相应的重载. 例如下面是正确的:

1
val l = 1L + 3 // Long + Int => Long

位操作符 对于Int和Long类型, 还有一系列的位操作符可以使用, 分别是:

1
2
3
4
5
6
7
shl(bits) – 左移位 (Java's <<)
shr(bits) – 右移位 (Java's >>)
ushr(bits) – 无符号右移位 (Java's >>>)
and(bits) – 与
or(bits) – 或
xor(bits) – 异或
inv() – 反向

字符

和 Java 不一样, Kotlin 中的 Char 不能直接和数字操作, Char 必需是单引号 ‘ 包含起来的. 比如普通字符 ‘0’, ‘a’.

1
2
3
4
5
fun check(c: Char) {
    if (c == 1) { // 错误:类型不兼容
        // ……
    }
}

字符字面值用单引号括起来: ‘1’. 特殊字符可以用反斜杠转义. 支持这几个转义序列:\t、 \b、\n、\r、'、"、\ 和 $. 编码其他字符要用 Unicode 转义序列语法:’\uFF00’.

我们可以显式把字符转换为 Int 数字:

1
2
3
4
5
fun decimalDigitValue(c: Char): Int {
    if (c !in '0'..'9')
        throw IllegalArgumentException("Out of range")
    return c.toInt() - '0'.toInt() // 显式转换为数字
}

当需要可空引用时, 像数字、字符会被装箱. 装箱操作不会保留同一性.

布尔

布尔用 Boolean 类型表示, 它有两个值:true 和 false.

若需要可空引用布尔会被装箱.

内置的布尔运算有:

1
2
3
|| – 短路逻辑或
&& – 短路逻辑与
! - 逻辑非

if

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 也可以用上文提到的in
fun main(args: Array<String>) {
    var x = 0
    if(x>0){
        println("x 大于 0")
    }else if(x==0){
        println("x 等于 0")
    }else{
        println("x 小于 0")
    }
    var a = 1
    var b = 2
    val c = if (a>=b) a else b
    println("c 的值为 $c")
}

// x 等于 0
// c 的值为 2

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
// when 类似其他语言的 switch 操作符,将它的参数和所有的分支条件顺序比较,直到某个分支满足条件
fun main(args: Array<String>) {
    var x = 0
    // when 既可以被当做表达式使用也可以被当做语句使用。如果它被当做表达式,符合条件的分支的值就是整个表达式的值,如果当做语句使用, 则忽略个别分支的值。

    when (x) {
        //如果很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔
        0, 1 -> println("x == 0 or x == 1")
        else -> println("otherwise")
    }

    when (x) {
        1 -> println("x == 1")
        2 -> println("x == 2")
        else -> { // 注意这个块
            println("x 不是 1 ,也不是 2")
        }
    }

    when (x) {
        in 0..10 -> println("x 在该区间范围内")
        else -> println("x 不在该区间范围内")
    }

    score = 60
    grade = if (score in 90..100) 'A'
    else if (score in 80..89) 'B'
    else if (score in 70..79) 'C'
    else if (score in 60..69) 'D'
    else 'F'
}
// x == 0 or x == 1
// x 不是 1 ,也不是 2
// x 在该区间范围内

fun main(args: Array<String>) {
    val items = setOf("apple", "banana", "kiwi")
    when {
        "orange" in items -> println("juicy")
        "apple" in items -> println("apple is fine too")
    }
}
// apple is fine too

函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 函数定义使用关键字 fun, 参数格式为: 参数 : 类型
fun sum(a: Int, b: Int): Int {   // Int 参数, 返回值 Int
    return a + b
}
// 参数必须写类型
// 表达式作为函数体, 返回类型自动推断,也就是仅仅以等号来写的可以不加返回类型
fun sum(a: Int, b: Int) = a + b
public fun sum(a: Int, b: Int): Int = a + b   // public 方法则必须明确写出返回类型

// 如果以花括号的形式定义函数,那么除非返回类型是Unit ,否则都要指明类型,不可自动推导
// 无返回值的函数(类似Java中的void):
fun printSum(a: Int, b: Int): Unit { 
    print(a + b)
}

// 如果是返回 Unit类型, 则可以省略(对于public方法也是这样):
public fun printSum(a: Int, b: Int) { 
    print(a + b)
}

类似

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
// var <propertyName>[: <PropertyType>] [= <property_initializer>]
//     [<getter>]
//     [<setter>]

// val修饰的变量是没有set方法的, 也不允许重写set方法

// field (后端变量)就是用来指代原变量的值的,即lastName是对外使用的名字
class Person {
    var lastName: String = "zhang"        
        // 这里使用field而不是使用lastName, 是因为如果使用lastName会造成递归调用从而造成内存溢出, 因为使用lastName也会涉及到调用set/get的问题
        get() = field.toUpperCase()   // 将变量赋值后转换为大写
        set

    var no: Int = 100
        get() = field                // 后端变量
        set(value) {
            if (value < 10) {       // 如果传入的值小于 10 返回该值
                field = value
            } else {
                field = -1         // 如果传入的值大于等于 10 返回 -1
            }
        }

    var heiht: Float = 145.4f
        private set
}

// 测试
fun main(args: Array<String>) {
    var person: Person = Person()

    person.lastName = "wang"

    println("lastName:${person.lastName}")

    person.no = 9
    println("no:${person.no}")

    person.no = 20
    println("no:${person.no}")

}

Null 检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Kotlin的空安全设计对于声明可为空的参数
// 在使用时要进行空判断处理,有两种处理方式,
// 字段后加!!像Java一样抛出空异常
// 另一种字段后加?可不做处理返回值为 null或配合?:做空判断处理

//类型后面加?表示可为空
var age: String? = "23" 
//抛出空指针异常
val ages = age!!.toInt()
//不做处理返回 null
val ages1 = age?.toInt()
//age为空返回-1
val ages2 = age?.toInt() ?: -1

// 当一个引用可能为 null 值时, 对应的类型声明必须明确地标记为可为 null
// 当 str 中的字符串内容不是一个整数时, 返回 null:

fun parseInt(str: String): Int? {
  // ...
}