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不会隐式的在类型转换时扩大数字的长度,并且在某些情况下数字的字面表示方式的方式也有些不同。Type | Bit width |
---|---|
Double | 64 |
Float | 32 |
Long | 64 |
Int | 32 |
Short | 16 |
Byte | 8 |
常量书写
书写整型常量有如下几种方式十进制书写: 123
– 使用
L书写十进制长整型: 123L
十六进制书写: 0x0F
二进制书写: 0b00001011
注: 不支持八进制书写方式.
Kotlin还支持传统的浮点型标记
– Doubles by default:
123.5,
123.5e10
– Floats are tagged by
for
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)”.
相关文章推荐
- 你应该学习哪种编程语言?
- [转]我们需要一种其他人能使用的编程语言
- ip地址基础知识
- VBS基础编程教程 (第1篇)
- VBS基础编程教程 (第3篇)
- 路由器基础精析
- VBS基础编程教程 (第4篇)
- VBS基础编程教程 (第5篇)
- VBS基础编程教程 (第6篇)
- VBS编程教程 (第2篇)
- C#语言主要特性总结
- AJAX初级教程之初识AJAX
- Jquery 基础学习笔记
- C语言中static的作用及C语言中使用静态函数有何好处
- PHP学习一(基础)第1/2页
- PHP检测用户语言的方法
- JS基础随笔(菜鸟必看篇)
- 《JavaScript DOM 编程艺术》读书笔记之DOM基础
- 精通Javascript系列之Javascript基础篇
- JavaScript 学习笔记之基础中的基础