Kotlin--基本

内部表达

在 Java 平台中, 数值的物理存储使用 JVM 的基本类型来实现, 但当我们需要表达一个可为 null 的数值引用时(比如. Int?), 或者涉及到泛型时, 我们就不能使用基本类型了. 这种情况下数值会被装箱(box)为数值对象.

注意, 数值对象的装箱(box)并不保持对象的同一性(identity):

val a: Int = 10000
print(a === a) // 打印结果为 'true'
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA === anotherBoxedA) // !!!打印结果为 'false'!!!

但是, 装箱(box)会保持对象内容相等(equality):

val a: Int = 10000
print(a == a) // 打印结果为 'true'
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA == anotherBoxedA) // 打印结果为 'true'

数组

Kotlin 中的数组通过 Array 类表达, 这个类拥有 get 和 set 函数(这些函数通过运算符重载转换为 [] 运算符), 此外还有 size 属性, 以及其他一些有用的成员函数:

class Array<T> private constructor() {
    val size: Int
    fun get(index: Int): T
    fun set(index: Int, value: T): Unit

    fun iterator(): Iterator<T>
    // ...
}

要创建一个数组, 我们可以使用库函数 arrayOf(), 并向这个函数传递一些参数来指定数组元素的值, 所以 arrayOf(1, 2, 3) 将创建一个数组, 其中的元素为 [1, 2, 3]. 或者, 也可以使用库函数 arrayOfNulls() 来创建一个指定长度的数组, 其中的元素全部为 null 值.

另一种方案是使用一个工厂函数, 第一个参数为数组大小, 第二个参数是另一个函数, 这个函数接受数组元素下标作为自己的输入参数, 然后返回这个下标对应的数组元素的初始值:

// 创建一个 Array<String>, 其中的元素为 ["0", "1", "4", "9", "16"]
val asc = Array(5, { i -> (i * i).toString() })

我们在前面提到过, [] 运算符可以用来调用数组的成员函数 get() 和 set().

注意: 与 Java 不同, Kotlin 中数组的类型是不可变的. 所以 Kotlin 不允许将一个 Array<String> 赋值给一个 Array<Any>, 否则可能会导致运行时错误(但你可以使用 Array<out Any>, 参见 类型投射).

Kotlin 中也有专门的类来表达基本数据类型的数组: ByteArrayShortArrayIntArray 等等, 这些数组可以避免数值对象装箱带来的性能损耗. 这些类与 Array 类之间不存在继承关系, 但它们的方法和属性是一致的. 各个基本数据类型的数组类都有对应的工厂函数:

val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]

字符串

字符串由 String 类型表示. 字符串的内容是不可变的. 字符串中的元素是字符, 可以通过下标操作符来访问: s[i]. 可以使用 for 循环来遍历字符串:

Kotlin 中存在两种字符串字面值: 一种称为转义字符串(escaped string), 其中可以包含转义字符, 
val s = "Hello, world!\n"
  
  
一种成为原生字符串(raw string), 其内容可以包含换行符和任意文本.  原生字符串(raw string)由三重引号表示( """ ), 其内容不转义, 可以包含换行符和任意字符:

val text = """
    for (c in "foo")
        print(c)
"""

字符串模板

字符串内可以包含模板表达式, 也就是说, 可以包含一小段代码, 这段代码会被执行, 其计算结果将被拼接为字符串内容的一部分. 模板表达式以 $ 符号开始, $ 符号之后可以是一个简单的变量名:

val i = 10
val s = "i = $i" // 计算结果为 "i = 10"

$ 符号之后也可以是任意的表达式, 由大括号括起:

val s = "abc"
val str = "$s.length is ${s.length}" // 计算结果为 "abc.length is 3"

原生字符串(raw string)和转义字符串(escaped string)内都支持模板. 由于原生字符串无法使用反斜线转

源代码文件的开始部分可以是包声明:

package foo.bar

fun baz() {}

class Goo {}

// ...

源代码内的所有内容(比如类, 函数)全部都包含在所声明的包之内. 因此, 上面的示例代码中, baz() 函数的完整名称将是 foo.bar.bazGoo 类的完整名称将是 foo.bar.Goo.

如果没有指定包, 那么源代码文件中的内容将属于 “default” 包, 这个包没有名称.

导入(Import)

除默认导入(Import)的内容之外, 各源代码可以包含自己独自的 import 指令. import 指令的语法请参见 语法.

我们可以导入一个单独的名称, 比如

import foo.Bar // 导入后 Bar 就可以直接访问, 不必指定完整的限定符

也可以导入某个范围(包, 类, 对象, 等等)之内所有可访问的内容:

import foo.* // 导入后 'foo' 内的一切都可以访问了

如果发生了名称冲突, 我们可以使用 as 关键字, 给重名实体指定新的名称(新名称仅在当前范围内有效):

import foo.Bar // 导入后 Bar 可以访问了
import bar.Bar as bBar // 可以使用新名称 bBar 来访问 'bar.Bar'

import 关键字不仅可以用来导入类; 还可以用来导入其他声明:

与 Java 不同, Kotlin 没有单独的 “import static” 语法; 所有这些声明都使用通常的 import 关键字来表达.

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

控制流

if 表达式

在 Kotlin 中, if 是一个表达式, 也就是说, 它有返回值. 因此, Kotlin 中没有三元运算符(条件 ? then 分支返回值 : else 分支返回值), 因为简单的 if 表达式完全可以实现同样的任务.

// if 的传统用法
var max = a 
if (a < b) max = b 
 
// 使用 else 分支的方式 
var max: Int
if (a > b) {
    max = a
} else {
    max = b
}
 
// if 作为表达式使用
val max = if (a > b) a else b

if 的分支可以是多条语句组成的代码段, 代码段内最后一个表达式的值将成为整个代码段的返回值:

val max = if (a > b) {
    print("Choose a")
    a
} else {
    print("Choose b")
    b
}

如果你将 if 作为表达式来使用(比如, 将它的值作为函数的返回值, 或将它的值赋值给一个变量), 而不是用作普通的流程控制语句, 这种情况下 if 表达式必须有 else 分支.

when 表达式

when 替代了各种 C 风格语言中的 switch 操作符. 最简单的形式如下例:

when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> { // 注意, 这里是代码段
        print("x is neither 1 nor 2")
    }
}

when 语句会将它的参数与各个分支逐个匹配, 直到找到某个分支的条件成立. when 可以用作表达式, 也可以用作流程控制语句. 如果用作表达式, 满足条件的分支的返回值将成为整个表达式的值. 如果用作流程控制语句, 各个分支的返回值将被忽略. (与 if 类似, 各个分支可以是多条语句组成的代码段, 代码段内最后一个表达式的值将成为整个代码段的值.)

如果其他所有分支的条件都不成立, 则会执行 else 分支. 如果 when 被用作表达式, 则必须有 else 分支, 除非编译器能够证明其他分支的条件已经覆盖了所有可能的情况.

如果对多种条件需要进行相同的处理, 那么可以对一个分支指定多个条件, 用逗号分隔:

when (x) {
    0, 1 -> print("x == 0 or x == 1")
    else -> print("otherwise")
}

在分支条件中, 我们可以使用任意的表达式(而不仅仅是常数值)

when (x) {
    parseInt(s) -> print("s encodes x")
    else -> print("s does not encode x")
}

我们还可以使用 in 或 !in 来检查一个值是否属于一个 范围, 或者检查是否属于一个集合:

when (x) {
    in 1..10 -> print("x is in the range")
    in validNumbers -> print("x is valid")
    !in 10..20 -> print("x is outside the range")
    else -> print("none of the above")
}

还可以使用 is 或 !is 来检查一个值是不是某个类型. 注意, 由于 Kotlin 的 智能类型转换 功能, 进行过类型判断之后, 你就可以直接访问这个类型的方法和属性, 而不必再进行显式的类型检查.

val hasPrefix = when(x) {
    is String -> x.startsWith("prefix")
    else -> false
}

when 也可以用来替代 if-else if 串. 如果没有指定参数, 那么所有的分支条件都应该是单纯的布尔表达式, 当条件的布尔表达式值为 true 时, 就会执行对应的分支:

when {
    x.isOdd() -> print("x is odd")
    x.isEven() -> print("x is even")
    else -> print("x is funny")
}

for 循环

任何值, 只要能够产生一个迭代器(iterator), 就可以使用 for 循环进行遍历. 语法如下:

for (item in collection) print(item)

循环体可以是多条语句组成的代码段.

for (item: Int in ints) {
    // ...
}

前面提到过, 凡是能够产生迭代器(iterator)的值, 都可以使用 for 进行遍历, 也就是说, 遍历对象需要满足以下条件:

  • 存在一个成员函数- 或扩展函数 iterator(), 它的返回类型应该:
    • 存在一个成员函数- 或扩展函数 next(), 并且
    • 存在一个成员函数- 或扩展函数 hasNext(), 它的返回类型为 Boolean 类型.

上述三个函数都需要标记为 operator.

for 循环遍历数组时, 会被编译为基于数组下标的循环, 不会产生迭代器(iterator)对象.

如果你希望使用下标变量来遍历数组或 List, 可以这样做:

for (i in array.indices) {
    print(array[i])
}

注意, 上例中的 “在数值范围内的遍历” 会在编译期间进行优化, 运行时不会产生额外的对象实例.

或者, 你也可以使用 withIndex 库函数:

for ((index, value) in array.withIndex()) {
    println("the element at $index is $value")
}

while 循环

while 和 do..while 的功能与其他语言一样:

while (x > 0) {
    x--
}

do {
    val y = retrieveData()
} while (y != null) // y is visible here!

返回与跳转

Kotlin 中存在 3 种程序流程跳出操作符

  • return. 默认行为是, 从最内层的函数或 匿名函数 中返回.
  • break. 结束最内层的循环.
  • continue. 在最内层的循环中, 跳转到下一次循环.

Break 和 Continue 的位置标签

Kotlin 中的任何表达式都可以用 label 标签来标记. 标签的形式与标识符相同, 后面附加一个 @ 符号, 比如: abc@fooBar@ 都是合法的标签(参见 语法). 要给一个表达式标记标签, 我们只需要将标签放在它之前:

loop@ for (i in 1..100) {
    // ...
}

然后, 我们就可以使用标签来限定 break 或 continue 的跳转对象:

loop@ for (i in 1..100) {
    for (j in 1..100) {
        if (...) break@loop
    }
}

通过标签限定后, break 语句, 将会跳转到这个标签标记的循环语句之后. continue 语句则会跳转到循环语句的下一次循环.

使用标签控制 return 的目标

在 Kotlin 中, 通过使用字面值函数(function literal), 局部函数(local function), 以及对象表达式(object expression), 允许实现函数的嵌套. 通过标签限定的 return 语句, 可以从一个外层函数中返回. 最重要的使用场景是从 Lambda 表达式中返回. 回忆一下我们曾经写过以下代码:

fun foo() {
    ints.forEach {
        if (it == 0) return
        print(it)
    }
}

这里的 return 会从最内层的函数中返回, 也就是. 从 foo 函数返回. (注意, 这种非局部的返回( non-local return), 仅对传递给 内联函数(inline function) 的 Lambda 表达式有效.) 如果需要从 Lambda 表达式返回, 我们必须对它标记一个标签, 然后使用这个标签来指明 return 的目标:

fun foo() {
    ints.forEach lit@ {
        if (it == 0) return@lit
        print(it)
    }
}

这样, return 语句就只从 Lambda 表达式中返回. 通常, 使用隐含标签会更方便一些, 隐含标签的名称与 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 语句, 也就是说:

return@a 1

含义是 “返回到标签 @a 处, 返回值为 1”, 而不是 “返回一个带标签的表达式 (@a 1)”.


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值