Kotlin Reference(二)-基础部分

Kotlin Reference(二)-基础部分

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


数据类型(Basic Types)

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

Numbers

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

TypeBit width
Double64
Float32
Long64
Int32
Short16
Byte8

注:在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 = 10000
print(a === a) // Prints 'true'
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA === anotherBoxedA) // !!!Prints 'false'!!!

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

val a: Int = 10000
print(a == a) // Prints 'true'
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(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 = 10
val 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.bar

fun 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 accessible
import 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 

// 使用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作为表达式而不是条件语句(例如,最为函数返回值或者给变量赋值),则这个表达式要求必须包含一个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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值