Kotlin Reference(二)-基础部分

来源:互联网 发布:学java好还是安卓好 编辑:程序博客网 时间:2024/05/22 03:48

Kotlin Reference(二)-基础部分

标签(空格分隔): 翻译 kotlin
译者:陈小默
版权声明:禁止商用,转载请注明出处


  • Kotlin Reference二-基础部分
  • 数据类型Basic Types
    • Numbers
      • 常量书写
      • 表现形式
      • 显式类型转换
      • 运算符
    • Character
    • Booleans
    • Arrays
    • Strings
      • 字符串的字面值
      • 字符串模板
  • 包Package
    • Imports
    • 顶级声明的可见范围
  • 控制流程
    • If 表达式
    • When 表达式
    • For 循环
    • While 循环
  • 返回和跳转
    • Break 和 Continue 标签
    • 使用标签的Return

数据类型(Basic Types)

在Kotlin中,万物皆对象,在此定义上我们可以访问任何变量的函数和属性。某些内建类型使用时看起来就像是普通的类一样。在这一节,我们将回去介绍这些类型:numbers, characters, booleans and arrays.

Numbers

Kotlin对于数字的处理十分接近java,但又不完全相同。例如,Kotlin不会隐式的在类型转换时扩大数字的长度,并且在某些情况下数字的字面表示方式的方式也有些不同。

Type Bit width Double 64 Float 32 Long 64 Int 32 Short 16 Byte 8

注:在Kotlin中字符不再表示为数字

常量书写

书写整型常量有如下几种方式

  • 十进制书写: 123
    – 使用L书写十进制长整型: 123L

  • 十六进制书写: 0x0F

  • 二进制书写: 0b00001011

注: 不支持八进制书写方式.

Kotlin还支持传统的浮点型标记

– Doubles by default: 123.5, 123.5e10
– Floats are tagged by f or F: 123.5f

表现形式

在Java平台上,数字是以物理形式存储在JVM当中的原始数据类型,除非我们需要一个允许为空的数字引用或者相关的类型。而后者是一种装箱类型。

注:数字装箱过程并不能保证数据一致:

val a: Int = 10000print(a === a) // Prints 'true'val boxedA: Int? = aval anotherBoxedA: Int? = aprint(boxedA === anotherBoxedA) // !!!Prints 'false'!!!

另一方面,他们保存的值是相等的:

val a: Int = 10000print(a == a) // Prints 'true'val boxedA: Int? = aval anotherBoxedA: Int? = aprint(boxedA == anotherBoxedA) // Prints 'true'

显式类型转换

由于不同的表示方式,较小的类型不会被看作是大类型的子类型。如果是的话,我们会遇到如下所示的问题:

// 假想代码,实际无法编译:val a: Int? = 1 // 装箱类型 (java.lang.Integer)val b: Long? = a // 隐士的数据转换产生一个Long的装箱类型 (java.lang.Long)print(a == b) // 令人意外的是,这里打印了‘false’因为Long的equals()方法会检查对方是否也是Long类型

这样一来,在发生类型转换的地方不仅是同一性,甚至连数据相等都无法保持了。

因此,小的数据类型无法被隐式的提升为较大的类型。这意味着我们不能明确指定一个Byte类型的数据赋值给Int变量

val b: Byte = 1 // 正确,字面值会被静态检查val i: Int = b // 错误

我们可以显式扩大数字的宽度

val i: Int = b.toInt() // 正确,显式扩大数字宽度

所有的数字类型都支持一下转换方式

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

隐式转换的缺乏并没有引起太大的注意,因为这些类型可以通过上下文被推断出来,并且这里的数学运算符为了适应类型的转换而进行了重载。例如:

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

运算符

Kotlin支持一套标准的数学运算符,这些运算被定义成了相关类的成员方法(但是编译器将这些函数优化为了对应的运算指令)

对于位运算符,没有用特别的字符去表示,而仅仅是用以函数名中缀的方式表示,例如

val x = (1 shl 2) and 0x000FF000

以下是位运算的完整列表 (仅适用于IntLong类型):

  • shl(bits) – 带符号左移 (Java’s <<)
  • shr(bits) – 带符号右移 (Java’s >>)
  • ushr(bits) – 无符号右移 (Java’s >>>)
  • and(bits) – 按位与 and
  • or(bits) – 按位或 or
  • xor(bits) – 按位异或 xor
  • inv() – 按位取反 inversion

Character

字符使用 Char 类型表达. 字符不能直接当作数值使用

fun check(c: Char) {  if (c == 1) { // 错误:类型不一致    // ...  }}

字符的字面值(literal)使用单引号表达: '1'. 特殊字符使用反斜线转义表达. Kotlin 支持的转义字符包括: \t, \b, \n, \r, \', \", \\ 以及 \$. 其他任何字符, 都可以使用 Unicode 转义表达方式: '\uFF00

我们可以显式的将字符转换为Int型数字

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

与数字一样,当需要一个可以为null的引用时,字符会被装箱。装箱操作同样不会保留同一性

Booleans

Boolean 类型用来表示布尔值, 有两个可能的值: truefalse.

当需要一个可为 null 的布尔值引用时, 布尔值也会被装箱(box).

布尔值的内建运算符包括

  • || – 短路或运算
  • && – 短路与运算
  • ! - 非运算

Arrays

Kotlin 中的数组通过 Array 类表达, 这个类拥有 getset函数(这些函数通过运算符重载转换为 [] 运算符), 此外还有 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 值.

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

// Creates an Array<String> with values ["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 中也有专门的类来表达基本数据类型的数组: ByteArray, ShortArray, IntArray 等等, 这些数组可以避免数值对象装箱带来的性能损耗. 这些类与 Array 类之间不存在继承关系, 但它们的方法和属性是一致的. 各个基本数据类型的数组类都有对应的工厂函数:

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

Strings

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

for (c in str) {  println(c)}

字符串的字面值

Kotlin 中存在两种字符串字面值: 一种称为转义字符串(escaped string), 其中可以包含转义字符, 另一种成为原生字符串(raw string), 其内容可以包含换行符和任意文本. 转义字符串(escaped string) 与 Java 的字符串非常类似:

val s = "Hello, world!\n"

转义字符使用通常的反斜线方式表示. 关于 Kotlin 支持的转义字符, 请参见上文的 Character 小节.

原生字符串(raw string)由三重引号表示("""), 其内容不转义, 可以包含换行符和任意字符:

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

你可以使用 trimMargin() 函数来删除字符串的前导空白

val text = """    |Tell me and I forget.     |Teach me and I remember.     |Involve me and I learn.    |(Benjamin Franklin)    """.trimMargin()

默认情况下, 会使用 | 作为前导空白的标记前缀, 但你可以通过参数指定使用其它字符, 比如 trimMargin(">").

字符串模板

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

val i = 10val s = "i = $i" // evaluates to "i = 10"

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

val s = "abc"val str = "$s.length is ${s.length}" // evaluates to "abc.length is 3"

原生字符串(raw string)和转义字符串(escaped string)内都支持模板. 由于原生字符串无法使用反斜线转义表达方式, 如果你想在字符串内表示 $ 字符本身, 可以使用以下语法:

val price = """${'$'}9.99"""

包(Package)

一个包的声明应该位于源文件的开始:

package foo.barfun baz() {}class Goo {}// ...

所有的一切内容(包括类和方法)都被包含在所声明的包之内。所以,在上面的示例中,方法 baz() 的全路径名为 foo.bar.bazGoo类的全路径名为 foo.bar.Goo

如果没有明确的指明包信息,则这些文件的全部内容将属于一个没有名字的默认包

Imports

除了默认导入的包之外,任何文件都可以有自己的import指令。

我们可以使用具体的名称导入一个单独的文件

import foo.Bar // Bar is now accessible without qualification

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

import foo.* // everything in 'foo' becomes accessible

如果发生名称冲突,我们可以使用一个as关键字重命名来区分冲突实体

import foo.Bar // Bar is accessibleimport bar.Bar as bBar // bBar stands for 'bar.Bar'

import关键字并不仅限于导入类;同样的,你也可以导入其他声明

  • 顶级(top-level)函数和属性
  • 类中声明的方法和属性
  • 枚举常量

不同于Java的是,Kotlin没有单独的“import static” 语法;所有的声明引入都使用同一的import关键字

顶级声明的可见范围

如果一个顶级声明被标记为private,那么他将只能在被声明的文件中访问,即成为私有

控制流程

If 表达式

在Kotlin中,if 是一个表达式,也就是说,他有返回值。因此Kotlin中取消了三元表达式(条件 ? then : else), 因为 if 能起到更好的作用。

//传统的使用方式 var max = a if (a < b)   max = b // 使用elsevar max: Intif (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作为表达式而不是条件语句(例如,最为函数返回值或者给变量赋值),则这个表达式要求必须包含一个else分支

When 表达式

when表达式替换了类C语言的switch-case表达式。一个最简单的例子看起来就像这样

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}

我们也可以用它来替代if-else if链式表达式。如果没有声明参数,则所有分支条件都是单纯的boolean表达式,并且当分支条件为true时执行分支。

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

For 循环

for循环能够迭代一切能产生迭代器的数据。语法如下所示

for (item in collection)  print(item)

函数体可以是代码块

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

就像前面提到的,for表达式可以迭代任何能产生迭代器的数据,也就是说参数需要提供一个iterator()方法,并且这个iterator()方法返回的数据包括一个能够产生数据的next()方法和一个返回Boolean类型的判断是否包含下一个的hasNext()方法:

上述三个方法被称作运算符

数组的循环过程被编译为一个基于索引的循环,这个循环并不会产生一个迭代器(iterator)对象

如果你想要使用索引去迭代一个数组或者是链表,你可以使用下面这种方式

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

注:这种“区间迭代”是一种在编译时不会产生额外对象的最佳实现方式

或者,你可以使用函数库中的withIndex()方法

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

While 循环

whiledo..while 和其他语言一样

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

返回和跳转

Kotlin提供三中跳转操作符

  • return. 默认跳出最近的方法或者匿名方法
  • break. 跳出最近的一层循环
  • continue. 结束最近一层的循环操作开始执行下一次循环

Break 和 Continue 标签

Kotlin中任何表达式都可以使用label去标记。标签的格式是 标识符后跟随一个@符号,例如abc@fooBar@ 都是可用的标签。使用标签表达式的时候,我们只需要将标签放在表达式的前面即可。

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

现在,我们就可以使用带标签的breakcontinue

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

使用标签的break将会跳出被标记的循环。而continue将会执行被标记循环的下一次循环。

使用标签的Return

在Kotlin中可以通过字面方法(function literals), 本地方法(local functions)和 对象表达式(object expression)进行函数嵌套。 使用标签的return允许我们从外层方法返回。最重要的方法是从lambda表达式中返回。回忆一下我们写过的代码

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

return表达式会从最近的方法放回,也就是foo方法(注意这种非局部返回仅对内联函数的lambda表达式有效) 如果我们想要从lambda表达式中返回,则需要在lambda表达式上使用标签

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@a 1

的含义是“将1返回到@a标签指定的方法” 而不是 “返回一个标签表达式 (@a 1)”.

0 0