您的位置:首页 > Web前端

Kotlin Reference(二)-基础部分

2016-07-28 11:48 141 查看

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不会隐式的在类型转换时扩大数字的长度,并且在某些情况下数字的字面表示方式的方式也有些不同。

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


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

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 类型用来表示布尔值, 有两个可能的值:
true
false
.

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

布尔值的内建运算符包括

||
– 短路或运算

&&
– 短路与运算

!
- 非运算

Arrays

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 值.

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

// 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.baz
Goo
类的全路径名为
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 循环

while
do..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) {
// ...
}


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


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)”.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  kotlin 基础 语言