Kotlin 中,我們可以調(diào)用任何變量的成員函數(shù)和屬性,從這個角度來說,一切皆對象。某些類型可以有特殊的內(nèi)部表現(xiàn) - 例如,數(shù)字、字符和布爾型在運(yùn)行時可以表現(xiàn)為基礎(chǔ)類型(primitive types),但是對用戶來說,他們看上去就是是普通的類。這一章節(jié)主要描述 Kotlin 的基本類型:數(shù)字、字符、布爾、數(shù)組和字符串。
數(shù)值
Kotlin 處理數(shù)字的方式與 Java 類似,但不是完全一致。例如,數(shù)值沒有隱式的拓寬轉(zhuǎn)換(implicit widening conversions),某些情況下,字面意思也會稍有不同。
Kotlin 提供了如下內(nèi)置類型來表示數(shù)值(接近 Java):
| 類型 | 位寬 |
|---|---|
| Double | 64 |
| Float | 32 |
| Long | 64 |
| Int | 32 |
| Short | 16 |
| Byte | 8 |
注意:字符不是一種數(shù)值。
字面常量
整形值的字面常量有如下形式:
- 十進(jìn)制:
123- 長整型用
L做標(biāo)記:123L
- 長整型用
- 十六進(jìn)制:
0x0F - 二進(jìn)制:
0b00001011
注意:不支持八進(jìn)制。
浮點數(shù)也支持約定的標(biāo)記:
- double 類型:
123.5,123.5e10 - float 用
f或者F標(biāo)記:123.5f
數(shù)值字面值中的下劃線(1.1開始)
下劃線可以使數(shù)值常量更具可讀性:
val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_D5_5E
val bytes = 0b11010010_01101001_10010100_10010010
表現(xiàn)形式
Java 平臺會把數(shù)值作為 JVM 基礎(chǔ)類型來物理存儲。除非是一個可為空的數(shù)值引用(例如 Int?)或者有泛型引入。如果是后者,數(shù)值會裝箱。
注意:裝箱后的數(shù)值不會保持 identity:
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'
顯示轉(zhuǎn)換
由于不同的表現(xiàn)形式,小類型并非大類型的子類型。如果是的話,可能會帶來如下麻煩:
// Hypothetical code, does not actually compile:
val a: Int? = 1 // A boxed Int (java.lang.Integer)
val b: Long? = a // implicit conversion yields a boxed Long (java.long.Long)
print(a == b) // Surprise! This prints "false" as Long's equals() check for other part to be Long as well
所以,不只是身份(identity),連相等性(equality)也會靜默丟失。
因此,小類型不會隱式轉(zhuǎn)換成大類型。這就意味著:不通過顯示轉(zhuǎn)換,我們無法把一個 Byte 賦值給 Int。
val b: Byte = 1 // OK, literals are checked statically
val i: Int = b // ERROR
通過顯示轉(zhuǎn)換可以“拓寬(widen)”數(shù)值。
val i: Int = b.toInt() // OK: explicitly widened
每個數(shù)值類型都支持如下轉(zhuǎn)換:
toByte(): BytetoShort(): ShorttoInt(): InttoLong(): LongtoFloat(): FloattoDouble(): DoubletoChar(): Char
缺少隱式轉(zhuǎn)換并不會引起注意,因為通過上下文可以推導(dǎo)出類型,并且算術(shù)操作符也有支持類型轉(zhuǎn)換的重載,例如:
val l = 1L + 3 // Long + Int => Long
運(yùn)算
Kotlin 支持?jǐn)?shù)值的標(biāo)準(zhǔn)算術(shù)運(yùn)算,這些運(yùn)算被聲明為相應(yīng)類的成員(但是編譯器會把函數(shù)調(diào)用優(yōu)化成相應(yīng)的指令)。參考操作符重載。
位運(yùn)算操作符也沒有特殊之處,他們也只是支持中綴調(diào)用的命名函數(shù),例如:
val x = (1 shl 2) and 0x000FF000
如下是位運(yùn)算操作符的完整列表(只用于 Int 和 Long):
-
shl(bits)- 有符號左移(Java 的<<) -
shr(bits)- 有符號右移(Java 的>>) -
ushr(bits)- 無符號右移(Java 的>>>) -
and(bits)- 位的與運(yùn)算 -
or(bits)- 位的或運(yùn)算 -
xor(bits)- 位的異或運(yùn)算 -
inv(bits)- 位的非運(yùn)算
浮點數(shù)比較
本節(jié)所要討論的浮點數(shù)運(yùn)算符有:
- 相等檢查:
a == b和a != b - 比較操作符:
a < b,a > b,a <= b,a >=b - 范圍初始化和范圍檢查:
a..b,x in a..b,x !in a..b
當(dāng)操作數(shù) a 和 b 靜態(tài)已知為類型 Float 或 Double,以及它們對應(yīng)的可空類型(得出方式包括:聲明、推斷或者智能轉(zhuǎn)換),數(shù)值的運(yùn)算以及它們形成的范圍(range)遵守 IEEE 754 制定的浮動點數(shù)運(yùn)算規(guī)范。
但是為了支持通用的使用場景以及提供完整的排序,當(dāng)操作數(shù)不是浮點數(shù)的靜態(tài)類型(如 Any、Comparable<...>,類型參數(shù))時,運(yùn)算操作會使用 Float 和 Double 的 equals 和 compareTo 實現(xiàn),這會導(dǎo)致異與標(biāo)準(zhǔn),因此:
-
NaN等于它自己 -
NaN大與所有其他元素,包括POSITIVE_INFINITY -
-0.0小于0.0
字符
Char 表示字符,不能直接用作數(shù)值:
fun check(c: Char) {
if (c == 1) { // ERROR: incompatible types
// ...
}
}
字符用單引號來表示:'1'。特殊字符可以使用反斜杠來轉(zhuǎn)義。
特殊字符可以用反斜杠轉(zhuǎn)義。支持的轉(zhuǎn)義序列有:\t、\b、\n、\r、\'、\"、\\、\$。如果要編譯其他字符,可以使用 Unicode 轉(zhuǎn)義序列語法:\uFF00。
我們可以顯示地把一個字符轉(zhuǎn)換成一個 Int 數(shù)值:
fun decimalDigitValue(c: Char): Int {
if (c !in '0'..'9')
throw IllegalArgumentException("Out of range")
return c.toInt() - '0'.toInt() // Explicit conversions to numbers
}
就像數(shù)值那樣,字符的空引用也會自動裝箱。裝箱操作不會保留字符的身份(identity)。
布爾型
Boolean 表示布爾型,有兩個值:true 和 false。
布爾的可空引用會自動裝箱。
內(nèi)置操作符包括:
-
||- lazy disjunction -
&&- lazy conjunction -
!- negation
數(shù)組
Kotlin 用類 Array 來表示數(shù)組,有 get 和 set 函數(shù)(利用操作符重載的約定可轉(zhuǎn)換成 [] 操作),還有 size 屬性,除此之外還有其他有用的成員函數(shù):
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>
// ...
}
使用庫函數(shù) arrayOf() 并傳入元素值可以創(chuàng)建一個數(shù)組:arrayOf(1, 2, 3) 創(chuàng)建了 [1, 2, 3]。另外,arrayOfNulls() 可以創(chuàng)建一個所有元素都是 null 的數(shù)組。
另一種創(chuàng)建方式是調(diào)用 Array 的構(gòu)造函數(shù),傳入數(shù)組大小和一個根據(jù)下標(biāo)返回初始值的函數(shù):
// Creates an Array<String> with values ["0", "1", "4", "9", "16"]
val asc = Array(5, { i -> (i * i).toString() })
上面已經(jīng)說過,[] 操作等價于調(diào)用成員函數(shù) get() 和 set()。
注意:與 Java 不同,Kotlin 的數(shù)組是不可變的(invariant)。這就意味著 Kotlin 不允許我們把 Array<String> 賦給 Array<Any>,這樣能避免運(yùn)行時的失?。ǖ悄苡?Array<out Any>,可參考類型映射)。
Kotlin 也有特定的類用于表示基礎(chǔ)類型數(shù)組(沒有裝箱的開銷):ByteArray、ShortArray、IntArray 等。這幾個類和 Array 沒有直接的繼承關(guān)系,但是他們有同樣的方法和屬性。每個類型都有相應(yīng)的工廠函數(shù):
val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]
字符串
字符串由 String 表示。字符串是不可變的。字符串的元素可通過下標(biāo)訪問:s[i]。字符串可通過 for 循環(huán)遍歷:
for (c in str) {
println(c)
}
字符串字面值
Kotlin 支持兩種類型的字符串字面值:包含轉(zhuǎn)義字符的轉(zhuǎn)義字符串和包含換行和任意文本的純字符串。轉(zhuǎn)義字符串跟 Java 類似:
val s = "Hello, world\n"
轉(zhuǎn)義遵守約定俗成的方式(利用 \)。上面的字符那一章節(jié)已經(jīng)列出了所有支持的轉(zhuǎn)義序列。
純字符串通過三個引號(""")來界定,它不會包含轉(zhuǎn)義而且能夠包含換行和任意字符:
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()
默認(rèn)情況下,| 用作 margin 前綴,但是也可以使用其他字符作為參數(shù)傳給 trimMargin,例如 trimMargin(">")。
字符串模板
字符串可以包含模板表達(dá)式,例如,可被求值的代碼片段,求值結(jié)果可以連接到字符串中。模板表達(dá)式以美元符號($)開始,由一個簡單的名稱組成:
val i = 10
val s = "i = $i" // evaluates to "i = 10"
或者是大括號內(nèi)的任意表達(dá)式:
val s = "abc"
val str = "$s.length is ${s.length}" // evaluates to "abc.length is 3"
模板可用于純字符串和轉(zhuǎn)義后的字符串內(nèi)。如果要在純字符串(不支持轉(zhuǎn)義)中展示 $ 符號,可以使用如下語法:
val price = """
${'$'}9.99
"""