本内容是建立在有java的基础上去学习Kotlin的这门语言的,所以更多的是记录一些与java不同的之处,或者是Kotlin的特性等。
基本类型
在 Kotlin 中,所有东西都是对象,在这个意义上讲我们可以在任何变量上调用成员函数和属性。 一些类型可以有特殊的内部表示——例如,数字、字符和布尔值可以在运行时表示为原生类型值,但是对于用户来说,它们看起来就像普通的类。 在本节中,我们会描述 Kotlin 中使用的基本类型:数字、字符、布尔值、数组与字符串。
数字
Kotlin 处理数字在某种程度上接近 Java,但是并不完全相同。例如,对于数字没有隐式拓宽转换(如 Java 中 int
可以隐式转换为long
——译者注),另外有些情况的字面值略有不同。
字面常量
数值常量字面值有以下几种:
- 十进制:
123
- Long 类型用大写
L
标记:123L
- Long 类型用大写
- 十六进制:
0x0F
- 二进制:
0b00001011
Kotlin 同样支持浮点数的常规表示方法:
- 默认 double:
123.5
、123.5e10
- Float 用
f
或者F
标记:123.5f
注意:在 Kotlin 中字符不是数字,且不支持八进制,且字符不是数值
表示方式
在 Java 平台数字是物理存储为 JVM 的原生类型,除非我们需要一个可空的引用(如 Int?
)或泛型。 后者情况下会把数字装箱
注意数字装箱不必保留同一性:
vala:Int=10000
print(a===a)// 输出“true”
valboxedA:Int?=a
valanotherBoxedA:Int?=a
print(boxedA===anotherBoxedA)// !!!输出“false”!!!
另一方面,它保留了相等性:
vala:Int=10000
print(a==a)// 输出“true”
valboxedA:Int?=a
valanotherBoxedA:Int?=a
print(boxedA==anotherBoxedA)// 输出“true”
类型转换
Kotlin 不能像java那样类型那样强制转换,int类型数据不能默认和long类型的数值进行比较大小。
比如在java中:
int a;
long b = 100;
a = b;(在kotlin中这样是不允许的,必须写成:a = b.toInt())
每个数字类型支持如下的转换:
toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char
但是在做计算的过程中,隐式转换又存在,比如:
vall=1L+3// Long + Int => Long
运算
Kotlin支持数字运算的标准集,运算被定义为相应的类成员(但编译器会将函数调用优化为相应的指令)。 参见运算符重载。
对于位运算,没有特殊字符来表示,而只可用中缀方式调用命名函数,例如:
val x = (1 shl 2) and 0x000FF000
这是完整的位运算列表(只用于 Int
和 Long
):
shl(bits)
– 有符号左移 (Java 的<<
)shr(bits)
– 有符号右移 (Java 的>>
)ushr(bits)
– 无符号右移 (Java 的>>>
)and(bits)
– 位与or(bits)
– 位或xor(bits)
– 位异或inv()
– 位非
浮点数比较
相等和大小的 比较与java相同,Kotlin增加了一种区间实例与区间检测,如下:
- 区间实例以及区间检测:
a..b
、x in a..b
、x !in a..b
字符
之前有提到过,字符不是数字
字符用 Char 类型表示。它们不能直接当作数字
fun check(c: Char) {
if (c == 1) { // 错误:类型不兼容
// ……
}
}
字符字面值用单引号括起来: '1'
。 特殊字符可以用反斜杠转义。 支持这几个转义序列:\t
、 \b
、\n
、\r
、\'
、\"
、\\
和 \$
。 编码其他字符要用 Unicode 转义序列语法:'\uFF00'
。
数组
数组在 Kotlin 中使用 Array 类来表示,它定义了 get 和 set 函数(按照运算符重载约定这会转变为 [])和 size 属性,以及一些其他有用的成员函数:
class Array<T> private constructor() {
val size: Int
operator fun get(index: Int): T
operator fun set(index: Int, value: T): Unit
operator fun iterator(): Iterator<T>
// ……
}
我们可以使用库函数 arrayOf() 来创建一个数组并传递元素值给它,这样 arrayOf(1, 2, 3) 创建了 array [1, 2, 3]。 或者,库函数 arrayOfNulls() 可以用于创建一个指定大小的、所有元素都为空的数组。
另一个选项是用接受数组大小和一个函数参数的工厂函数,用作参数的函数能够返回给定索引的每个元素初始值:
// 创建一个 Array<String> 初始化为 ["0", "1", "4", "9", "16"]
val asc = Array(5, { i -> (i * i).toString() })
如上所述,[] 运算符代表调用成员函数 get() 和 set()。
注意: 与 Java 不同的是,Kotlin 中数组是不型变的(invariant)。这意味着 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]
字符串
s[i]
。 可以用 for 循环迭代字符串:
for(cinstr){
println(c)
}
原生字符串 使用三个引号("""
)分界符括起来,内部没有转义并且可以包含换行和任何其他字符:
valtext="""
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(">")。
备注:前导空格,我的理解就是去除“|”前面的空格
("ABC\n123\n456"===("""ABC
|123
|456""".trimMargin())) // 结果为false
("ABC\n123\n456"==("""ABC
|123
|456""".trimMargin())) // 结果为true
字符串模板
字符串可以包含模板表达式 ,即一些小段代码,会求值并把结果合并到字符串中。 模板表达式以美元符($
)开头,由一个简单的名字构成:
vali=10vals="i = $i"// 求值结果为 "i = 10"
或者用花括号括起来的任意表达式:
vals="abc"
valstr="$s.length is ${s.length}"// 求值结果为 "abc.length is 3"
原生字符串和转义字符串内部都支持模板。 如果你需要在原生字符串中表示字面值 $
字符(它不支持反斜杠转义),你可以用下列语法:
valprice="""
${'$'}9.99
"""
包与导入
与 Java 不同的是Kotlin 没有单独的“import static”语法; 所有这些声明都用 import
关键字导入。
控制流:if、when、for、while
在 Kotlin 中,if是一个表达式,即它会返回一个值。 因此就不需要三元运算符(条件 ? 然后 : 否则),因为普通的 if 就能胜任这个角色。
例如:
传统if:
varmax:Int
if(a>b){
max=a
}else{
max=b
}
可以写成:
valmax=if(a>b)aelseb
if的分支可以是代码块,最后的表达式作为该块的值:
valmax=if(a>b){
print("Choose a")
a
}else{
print("Choose b")
b
}
备注: 如果你使用 if 作为表达式而不是语句(例如:返回它的值或者把它赋给变量),该表达式需要有 else
分支。
When 表达式
when 取代了类 C 语言的 switch 操作符。其最简单的形式如下:
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){
in1..10->print("x is in the range")
invalidNumbers->print("x is valid")
!in10..20->print("x is outside the range")
else->print("none of the above")
}
另一种可能性是检测一个值是(is)或者不是(!is)一个特定类型的值。注意: 由于智能转换,你可以访问该类型的方法和属性而无需任何额外的检测。
funhasPrefix(x:Any)=when(x){
isString->x.startsWith("prefix")
else->false
}
when 也可以用来取代 if-else if链。 如果不提供参数,所有的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支:
when{
x.isOdd()->print("x is odd")
x.isEven()->print("x is even")
else->print("x is funny")
}
For 循环
for 循环可以对任何提供迭代器(iterator)的对象进行遍历,这相当于像 C# 这样的语言中的 foreach
循环。语法如下:
for(itemincollection)print(item)
或者代码块
for(item:Intinints){
//……
}
for 可以循环遍历任何提供了迭代器的对象。即:
有一个成员函数或者扩展函数 iterator()
,它的返回类型有一个成员函数或者扩展函数 next()
,并且有一个成员函数或者扩展函数 hasNext()
返回 Boolean
。
如果你想要通过索引遍历一个数组或者一个 list,你可以这么做:
for(iinarray.indices){
print(array[i])
}
注意:这种“在区间上遍历”会编译成优化的实现而不会创建额外对象。
或者你可以用库函数 withIndex
:
for((index,value)inarray.withIndex()){
println("the element at $index is $value")
}
While 循环
while 和 do..while 照常使用
备注:在循环中 Kotlin 支持传统的 break 和 continue 操作符
Break 与 Continue 标签
在 Kotlin 中任何表达式都可以用标签(label)来标记。 标签的格式为标识符后跟 @
符号,例如:abc@
、fooBar@
都是有效的标签。 要为一个表达式加标签,我们只要在其前加标签即可。
loop@for(iin1..100){
// ……
}
现在,我们可以用标签限制 break 或者continue:
loop@for(iin1..100){
for(jin1..100){
if(……)break@loop
}
}
标签限制的 break 跳转到刚好位于该标签指定的循环后面的执行点。 continue 继续标签指定的循环的下一次迭代。
标签处返回
Kotlin 有函数字面量、局部函数和对象表达式。因此 Kotlin 的函数可以被嵌套。 标签限制的 return 允许我们从外层函数返回。 最重要的一个用途就是从 lambda 表达式中返回。
funfoo(){
ints.forEach{
if(it==0)return// nonlocal return from inside lambda directly to the caller of foo()
print(it)
}
}
这个 return 表达式从最直接包围它的函数即 foo
中返回。 (注意,这种非局部的返回只支持传给内联函数的 lambda 表达式。) 如果我们需要从 lambda 表达式中返回,我们必须给它加标签并用以限制 return。
funfoo(){
ints.forEachlit@{
if(it==0)
return@lit
print(it)
}
}
或者简写成funfoo(){
ints.forEach{
if(it==0)return@forEach
print(it)
}
}
或者,我们用一个匿名函数替代 lambda 表达式。 匿名函数内部的 return 语句将从该匿名函数自身返回
funfoo(){ints.forEach(fun(value:Int){
if(value==0)return// local return to the caller of the anonymous fun,
i.e. the forEach loop
print(value)
})
}
当要返一个回值的时候,解析器优先选用标签限制的 return,即
return@a1
意为“从标签 @a
返回 1”,而不是“返回一个标签标注的表达式 (@a 1)
”。