本文翻译自Kotlin官方学习文档:https://kotlinlang.org/docs/kotlin-docs.pdf
基础类型
在Kotlin语言中,可以从任何变量调用成员方法和属性,从这一点来说,任何事物都是对象。一些类型是内置对象,因为它们的实现经过优化,但对于用户来说,它们就和普通的类一样。在这一节中,我们将这些类型的大部分描述为:数字类型,字符类型,布尔类型和数组类型
- 数字类型
Kotlin处理数字的方式和Java很接近,但是不完全相同。比如,没有数字的隐式拓宽类型转换,在一些情况下,文字也有稍微的不同
Kotlin提供了以下几种内置的类型表示数字(这点和Java类似):
类型 位宽
——————
Double 64
——————
Float 32
——————
Long 64
——————
Int 32
——————
Short 16
——————
Byte 8
注意在Kotlin中字符不是数字类型
- 字面常量
整数值有以下几种字面常量:
—十进制数: 123
—Long类型的数用大写L标识 : 123L
—十六进制数: 0x0F
—二进制数: 0b00001011
注意:不支持八进制字面量
Kotlin也支持传统的符号表示浮点型数字:
—默认为Double类型: 123.5 , 123.5e10
—Float类型使用f或F标识 : 123.5f
- 在数字字面量上加下划线(从1.1开始)
可以使用下划下使数字常量更具可读性:
val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010
- 表示法
在Java平台上,数字是作为JVM的原始类型进行物理存储的,除非需要一个非空的数字引用(例如:Int?)或有泛型包含在内。在后一种情况下,是将数字进行装箱。
注意装箱的数字没有必要保持一致:
val a: Int = 10000
print(a === a) // 打印'true'
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA === anotherBoxedA) // !!!打印'false'!!!
另一方面,保持相等:
val a: Int = 10000
print(a == a) // 打印'true'
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA == anotherBoxedA) // 打印'true'
- 显式转换
由于不同的表示法,小的类型不是大类型的子类型。如果是的话,就没有下面这些麻烦了:
// 假设代码,没有实际编译:
val a: Int? = 1 // 装箱为Int (java.lang.Integer)
val b: Long? = a // 隐式转换装箱为Long (java.lang.Long)
print(a == b) // 意外! 当equals()方法检查其他部分为Long时,也会输入"false"
所以不仅是一致性,就算等同也会在此默认丢失一部分
作为结果,小的类型不可以隐式地转换为较大的类型。这意味着如果没有经过显式转换,我们不能给一个Int类型的变量指定一个Byte类型的值
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支持一套对数字的标准算数操作,这些算数操作被声明为响应类的成员(但是编译器根据对应的指定对调用进行优化)。详见Operator overloading
对于位运算,没有特殊字符的要求,但是只有命名的函数可以以中缀方式被调用,例如:
val x = (1 shl 2) and 0x000FF000
这是完整的位运算列表:
shl(bits) – 左移位运算 (相当于Java的 << )
shr(bits) – 右移位运算 (相当于Java的 >> )
ushr(bits) – 无符号的右移位运算 (相当于Java的 >>> )
and(bits) – 位运算”与”
or(bits) – 位运算”或”
xor(bits) – 位运算”异或”
inv() – 位运算”取反”
- 字符型
Char代表字符类型,不可以直接作为数字来对待
fun check(c: Char) {
if (c == 1) { // 错误:类型不相容
// ...
}
字符是在单引号中:’1’。特殊字符可以使用反斜线转义。下面的转义序列是支持的:\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引用时,字符被装箱。装箱时不保证一致性
- 布尔型
Boolean代表布尔类型,有两个值:true和false
如果需要null引用,布尔值被装箱
内置的布尔操作符包括
— || – 懒惰式分割
— && –懒惰式连接
— ! - 否定
- 数组
Kotlin中,使用Array类来表示数组,它有get和set方法(通过约定的操作符重载转换为[]),以及合适的长度,和一些其他有用的成员方法:
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)创建了一个数组[1, 2, 3]。另外,库函数arrayOfNulls()可以用来创建一个给定大小,使用null值填充的数组
另一种方法是使用工厂方法获取数组大小,方法中返回每个指定索引位置数组元素的初始值:
// 创建一个Array<String>,元素值为 ["0", "1", "4", "9", "16"]
val asc = Array(5, { i -> (i * i).toString() })
像上面所说的,[]操作表示调用get()和set()成员方法
注意:和Java不一样,Kotlin中的数组是不可变得。这意味着Kotlin不允许给Array指定一个Array,这避免了一个运行时可能出现的错误(但是可以使用Array,详见 Type Projections)
Kotlin也有专门的类表示原始类型的数组,没有装箱的开销:ByteArray,ShortArray,IntArray等。这些类和Array类没有继承关系,但是它们有相同的一系列方法和属性。每个类也有对应的工厂方法:
val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]
- 字符串
String用来表示字符串。字符串是不可变得。字符串的元素是字符,可以通过索引访问:s[i]。一个字符串可以使用for循环遍历:
for (c in str) {
println(c)
}
- 字符串文字
Kotlin有两种字符串文字:包含转义字符的转义字符串和可以包含新行、任意文本的原始字符串。转义字符串和Java的字符串很像:
val s = "Hello, world!\n"
转义用传统的方式实现,使用反斜杠。支持的转义序列列表,参见上面的Characters,原始字符串使用三引号(”“”)进行分隔,不包含转义,可以包含新的行和任意字符
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" // 结果为 "i = 10"
或者花括号里的任意表达式:
val s = "abc"
val str = "$s.length is ${s.length}" // 结果为 "abc.length is 3"
模板支持内置原始字符串和内置转义字符串。如果需要在原始字符串(不支持反斜杠转义)中表示”$”字符,可以使用如下语法:
val price = """
${'$'}9.99
"""
包
源文件以包声明开始:
package foo.bar
fun baz() {}
class Goo {}
// ...
源文件的所有内容(类和方法)都包含在包的声明中。所以,在上面的例子中,baz()的全名为foo.bar.baz,Goo的全名为foo.bar.Goo
如果没有指定包,文件默认属于没有名称的”default”包
- 默认导入
Kotlin文件默认导入一些包:
—kotlin.*
—kotlin.annotation.*
—kotlin.collections.*
—kotlin.comparisons.* (since 1.1)
—kotlin.io.*
—kotlin.ranges.*
—kotlin.sequences.*
—kotlin.text.*
根据平台会导入一些额外的包:
—JVM:
java.lang.*
kotlin.jvm.*
—JS:
kotlin.js.*
- 导入包
除默认导入的包,每个文件包含自己的导入准则。导入包的语法在”语法”中已经描述过。
我们也可以导入单个名称,如:
import foo.Bar // Bar类现在可以被使用
或者范围内(包,类,对象等)所有可以访问的内容:
import foo.* // 'foo'内的所有内容可以访问
如果有命名冲突,我们可以使用as关键字进行本地重命名类消除歧义:
import foo.Bar // Bar类可访问
import bar.Bar as bBar // bBar代表访问'bar.Bar'类
import关键字不是被限制于引入类,也可以使用它来引入其他声明:
—顶级函数和属性
—对象中声明的方法和属性
—枚举常量
和Java不同,Kotlin没有单独的”import static”语法,所有的声明使用import关键字引入
- 顶级声明的可见性
如果一个顶级声明被标记为private,它在被声明的文件里就是private访问权限(详见 Visibility Modifiers)