KOTLIN開發語言文檔(官方文檔)第二章、基本概念
網頁鏈接:https://kotlinlang.org/docs/reference/basic-types.html
2. 基本概念
2.1. 基本類型
從可以在任何變數處理調用成員函數和屬性角度來說,在Kotlin開發語言中,一切都是對象。有些類型是內嵌的,它們的實現進行過優化,用戶看到的仍是原始類。在這節中,我們說明大部分這些類型:數字,字元,布爾和數組。
2.1.1. 數字
Kotlin開發語言處理數組的方法類似Java開發語言,但是也有差別。例如,沒有隱含的數字擴寬的轉換,並且在相同的情況下,文字也有些不同。
Kotlin開發語言提供下列內嵌類型表示數字(這是類似Java開發語言):
類型 |
位寬度 |
Double |
64 |
Float |
32 |
Long |
64 |
Int |
32 |
Short |
16 |
Byte |
8 |
註意,在Kotlin開發語言中,字元不屬於數組。
2.1.1.1. 文字常數
對於整數值有下麵幾種文字常數:
—— 十進位數:123
—— 附加大寫字母L標準長整數:123L
—— 十六進位數:0x0F
—— 二進位數:0b00001011
註意:不支持八進位數字。
Kotlin開發語言還支持通用的浮點數表示法:
—— 預設是雙精度:123.5,123.5e10
—— 附加f或F表示單精度浮點數:123.5F
2.1.1.2. 表示法
在Java開發語言平臺上,數字是作為JVM基本類型進行物理存在的,除非需要一個可null數字引用(如:Int?)或包含在泛型。後一種情況下,是將數字裝箱的。
註意數字裝箱不保持一致性:
1 val a: Int = 10000 2 print(a === a) // 列印 'true' 3 val boxedA: Int? = a 4 val anotherBoxedA: Int? = a 5 print(boxedA === anotherBoxedA) // !!!列印 'false'!!!
而另一方面,卻保持相等:
1 val a: Int = 10000 2 print(a == a) // 列印 'true' 3 val boxedA: Int? = a 4 val anotherBoxedA: Int? = a 5 print(boxedA == anotherBoxedA) // 列印 'true'
2.1.1.3. 顯式轉換
由於不同的表示法,較小類型不是較大類型的子類型。如果是,就有下列麻煩了:
1 // 假設代碼,沒有實際編譯: 2 val a: Int? = 1 // 裝箱為Int (java.lang.Integer) 3 val b: Long? = a // 隱含轉換裝箱為Long (java.lang.Long) 4 print(a == b) // 令人驚訝! 當equals()檢查其它部分不是Long,就列印"false"
所以不僅僅是一致性,而且即使相當也會在此默默丟失一部分。
這樣,較小的類型不會隱含的轉換為較大的類型。這就是說,如果沒有進行明確的類型轉換,Byte類型值是不能賦值給Int類型變數。
1 val b: Byte = 1 // OK, 靜態文字檢查 2 val i: Int = b // 錯誤
可以進行明確(顯式)的數字寬度轉換:
1 val i: Int = b.toInt() // OK: 顯式寬度轉換
每項數字類型支持下列轉換:
toByte(): Byte
—toShort(): Short
—toInt(): Int
—toLong(): Long
—toFloat(): Float
—toDouble(): Double
—toChar(): Char
因為類型是從上下文推斷,以及適當的轉換重載了算術運算符,所以隱式轉換缺位是很少引人註目的,例如:
1 val l = 1L + 3 // Long + Int => Long
2.1.1.4. 運算
Kotlin開發語言支持對數字標準的一套運算,它們被聲明為相應類成員(但是,編譯器優化調用的相應指令)。查看:運算符重載(5.6)。
作為位運算,對於它們沒有特殊特性(字元),僅僅命名函數使其能以中綴方式被調用,如:
1 val x = (1 shl 2) and 0x000FF000
這是完整的位運算列表(僅僅對Int和Long類型有效):
— shl(bits) – signed shift left (Java’s <<)
— shr(bits) – signed shift right (Java’s >>)
— ushr(bits) – unsigned shift right (Java’s >>>)
— and(bits) – bitwise and
— or(bits) – bitwise or
— xor(bits) – bitwise xor
— inv() – bitwise inversion
2.1.2. 字元
char類型表示字元。它們不能直接作為數組處理:
1 fun check(c: Char) { 2 if (c == 1) { // ERROR: incompatible types 3 // ... 4 } 5 }
字元文字是在單引號中:’1’,’\n’,’\uFF00’。我們能夠明確地轉換字元到Int數字:
1 fun decimalDigitValue(c: Char): Int { 2 if (c !in '0'..'9') 3 throw IllegalArgumentException("Out of range") 4 return c.toInt() - '0'.toInt() // Explicit conversions to numbers 5 }
在需要可null引用時,像數字、字元是被裝箱。裝箱操作是不保證一致性的。
2.1.3. 布爾值
Boolean類型表示布爾值,它有兩個值:true和false。
如果需要可null引用時,布爾值可以被裝箱的。
布爾值的內置的運算包括:
— || – lazy分離 (註:這個“lazy”不知道怎樣翻譯好,就是 “或”為啥要這樣?)
— && – lazy連接
— ! – 非
2.1.4. 數組
在Kotlin開發語言中,Array類表示數組,它有get和set函數(即通過操作符重載約定轉成[]),有size屬性,以及其他一些有用的成員函數:
1 class Array<T> private constructor() { 2 val size: Int 3 fun get(index: Int): T 4 fun set(index: Int, value: T): Unit 5 6 fun iterator(): Iterator<T> 7 // ... 8 }
可以用庫函數arrayOf(),將數組各項的數值傳遞給它,來創建一個數組,如:arrayOf(1,2,3)創建數組[1,2,3]。或者,用arrayOfNulls()庫函數創建一個指定尺寸(size)的數組,其元素均填充為null。
另一種選擇是用工廠函數獲得數組尺寸,並且返回指定索引位置的數組元素的初始值:
1 // Creates an Array<String> with values ["0", "1", "4", "9", "16"] 2 val asc = Array(5, { i -> (i * i).toString() })
如上所述,[]操作符表示調用成員函數get()和set()。
註意:與Java開發語言不同,在Kotlin開發語言中,數組是不變數。這意味著kotlin開發語言不允許賦值Array<String>到Array<Any>,這防止運行的可能的錯誤(但是,可以用Array<out Any>,查看:類型推測(3.7.2))。
Kotlin開發語言也有專用類表示原始類型的數組,不需要裝箱消耗:ByteArray、ShortArray、IntArray等等。這些類與Array類沒有繼承關係,但是它們有相同的一組方法和屬性。它們中的每一個都有相應的工廠函數:
1 val x: IntArray = intArrayOf(1, 2, 3) 2 x[0] = x[1] + x[2]
2.1.5. 串
String類型表示串。String是不可變的。串的元素是字元,可以用索引操作訪問:s[i]。可以用for迴圈遍歷一個串:
1 for (c in str) { 2 println(c) 3 }
2.1.5.1. 串文字
Kotlin開發語言有兩種串文字類型:包含轉義字元的轉義串和包含任意字元和新行符的原始串。轉義串非常像Java開發語言的串:
1 val s = "Hello, world!\n"
轉義可以用習慣的方法(用\)實現。
原始串由三引號(”””)定界的,包含非轉義字元、新行符,以及其它任意字元:
1 val text = """ 2 for (c in "foo") 3 print(c) 4 """
2.1.5.2. 串模板
串可以包含模板表達式,即:可計算的代碼片段,其結果鏈接到串中。模板表達式以美元符號($)開始,和簡單的名字構成:
1 val i = 10 2 val s = "i = $i" // 計算結果是 "i = 10"
或是在大括弧中的任意表達式:
1 val s = "abc" 2 val str = "$s.length is ${s.length}" // 計算結果是 "abc.length is 3"
在原始串和轉義串中,都支持模板。如果需要表達$字元文字,則可以用下列語法:
1 val price = "${'$'}9.99"
2.2. 包
一個源文件可以是從聲明包開始的:
1 package foo.bar 2 3 fun baz() {} 4 class Goo {} 5 // ...
源文件中的所有內容(如:類和函數)都包含在包的聲明中。所以,在上面例子中,baz()的完整名稱是foo.bar.baz,Goo的完整名稱是foo.bar.Goo。
如果源文件中沒有指明包,則這個文件中的內容屬於沒有名稱的“預設”包。
2.2.1. 導入(import)
除預設的import外,每個文件都可以有自己的import偽指令。Import的句法在語法(6.2)中描述了。
我們既可以導入單個名稱,如:
1 import foo.Bar // Bar is now accessible without qualification
也可以是範圍內所有可訪問的內容(包、類、對象等等):
1 import foo.* // everything in 'foo' becomes accessible
如果有命名衝突,可以在衝突項用as關鍵字重命名來消除:
1 import foo.Bar // Bar可以訪問 2 import bar.Bar as bBar // bBar 表示 'bar.Bar'
import關鍵字不限制導入的類;也可以用途導入其它聲明:
—— 頂層函數和屬性;
—— 在對象聲明(3.12.2)中聲明的函數和屬性;
—— 枚舉常數(3.11);
不像Java開發語言,Kotlin開發語言沒有獨立的“import static”句法;所有這些聲明都是用常規的import關鍵字來導入。
2.2.2. 頂層聲明的可視範圍
如果頂層聲明標註private,它是它所在文件的私有的(查看:可視性修飾符(3.4))
2.3. 控制流
2.3.1. if表達式
在Kotlin開發語言中,if是一個表達式,即:它返回一個值。由於在此規則下普通if運行的很好,因此沒有三元運算符(?:else)。
1 // 傳統用法 2 var max = a 3 if (a < b) 4 max = b 5 6 // 帶else 7 var max: Int 8 if (a > b) 9 max = a 10 else 11 max = b 12 13 // 作為表達式 14 val max = if (a > b) a else b
if分支可以是代碼塊,最後表達式是代碼塊的值:
1 val max = if (a > b) { 2 print("Choose a") 3 a 4 } 5 else { 6 print("Choose b") 7 b 8 }
如果用if作為表達式,而不是語句(例如,返回它的值,或賦值給變數),表達式要求有else分支。
查看:if語法(6.2.3.4)
2.3.2. when表達式
When替代了類似C開發語言的switch操作符。最簡單形式如此:
1 when (x) { 2 1 -> print("x == 1") 3 2 -> print("x == 2") 4 else -> { // Note the block 5 print("x is neither 1 nor 2") 6 } 7 }
when將變數與其的所有分支順序逐一匹配,直至找到條件相符的分支。when即可用作表達式,也可以用作語句,滿足條件的分支值就是整個表達式的值。如果它用作語句,個別分支的值將被忽略。(就如同if,每個分支可以是一個代碼塊,代碼塊中最後的表達式值就是其值。)
else分支等價與沒有其它分支滿足條件。如果when用作一個表達式,且編譯器無法驗證分支條件覆蓋了所有的可能情況,則強制性要求else分支。
如果多種情況都有相同的處理方法,也可以用逗號將分支條件組合起來:
1 when (x) { 2 0, 1 -> print("x == 0 or x == 1") 3 else -> print("otherwise") 4 }
可以用任意表達式(不僅僅是常數)作為分支條件:
1 when (x) { 2 parseInt(s) -> print("s encodes x") 3 else -> print("s does not encode x") 4 }
還可以用in或!in檢查一個範圍(5.2)或集合:
1 when (x) { 2 in 1..10 -> print("x is in the range") 3 in validNumbers -> print("x is valid") 4 !in 10..20 -> print("x is outside the range") 5 else -> print("none of the above") 6 }
另一種情況,可以用is或!is檢查特別類型。註意,由於智能轉換(5.4),不需要任何額外的檢查就可以訪問類型的方法和屬性:
1 val hasPrefix = when(x) { 2 is String -> x.startsWith("prefix") 3 else -> false 4 }
when還可以用來替換if-else鏈。如果沒有變數,分支條件就是簡單的布爾表達式,且在when條件為true時,執行該分支:
1 when { 2 x.isOdd() -> print("x is odd") 3 x.isEven() -> print("x is even") 4 else -> print("x is funny") 5 }
查看:when語法(6.2.3.4.2.1)。
2.3.3. for迴圈
for迴圈遍歷提供的任何一個迭代器。句法如下:
1 for (item in collection) 2 print(item)
迴圈體可以是一個代碼塊。
1 for (item: Int in ints) { 2 // ... 3 }
如前所述,for迴圈遍歷提供的任何一個迭代器,即:
—— 有成員iterator()或擴展函數iterator(),它返回類型:
—— 有成員next()或擴展函數next(),和
—— 有返回布爾類型的成員hasNext()或擴展函數hasNext()。
所有這三個函數是需要作為操作符的。
如果要利用索引遍歷一個數組或列表,可以這樣做:
1 for (i in array.indices) 2 print(array[i])
註意,這句“遍歷一個範圍”是由編譯器優化實現的,不需要產生額外的對象。
或者,可以用withIndex庫函數:
1 for ((index, value) in array.withIndex()) { 2 println("the element at $index is $value") 3 }
查看:for語法(6.2.3.3)。
2.3.4. while迴圈
while和do…while都是如常規一樣工作:
1 while (x > 0) { 2 x-- 3 } 4 5 do { 6 val y = retrieveData() 7 } while (y != null) // y is visible here!
查看:while語法(6.2.3.3)。
2.3.5. 迴圈的中斷和繼續
Kotlin開發語言支持迴圈中的傳統break和continue操作符。查看:返回和跳轉(2.4)。
2.4. 返回和跳轉
Kotlin開發語言有三種結構化的跳轉操作符:
—— return。預設情況下,由最近的函數返回,或匿名函數的返回。
—— break。終止最近一層迴圈。
—— continue。繼續最近一層迴圈的下一步。
2.4.1. 中斷和繼續標簽
在Kotlin開發語言中,任何表達式都可以帶標簽。標簽的格式是在標識符後跟@來表示,如:abc@,fooBar@都是合法的標簽(查看:語法(6.2))。為了標記表達式,只需要在其前面加上標簽即可:
1 loop@ for (i in 1..100) { 2 // ... 3 }
現在,就可以break或continue到標簽了:
1 loop@ for (i in 1..100) { 2 for (j in 1..100) { 3 if (...) 4 break@loop 5 } 6 }
帶有標簽的break跳轉到標記loop之後的執行點。Continue繼續進行標記loop的下一步。
2.4.2. 在標簽處返回
Kotlin開發語言在函數體、局部函數和對象表達式中,允許函數嵌套。Return允許我們有外部函數返回。最重要的用例是由Lambda表達式返回。我們這樣編寫回調:
1 fun foo() { 2 ints.forEach { 3 if (it == 0) return 4 print(it) 5 } 6 }
返回表達式是由最近函數返回,即:foo。(註意:這樣對Lambda表達式僅支持非局部返回到內嵌函數(4.1.5)。)如果要從Lambda表達式返回,就需要標記它限制返回:
1 fun foo() { 2 ints.forEach lit@ { 3 if (it == 0) return@lit 4 print(it) 5 } 6 }
現在,就僅從Lambda表達式返回。通常,最方便的是用隱含標簽:這樣標簽與傳遞給Lambda表達式的函數同名。
1 fun foo() { 2 ints.forEach { 3 if (it == 0) return@forEach 4 print(it) 5 } 6 }
或者,可以用匿名函數(4.2.3.3)替代Lambda表達式。在匿名函數中的return語句是從匿名函數自身返回。
1 fun foo() { 2 ints.forEach(fun(value: Int) { 3 if (value == 0) return 4 print(value) 5 }) 6 }
當返回一個值是,解析器優先給出恰當的返回,如:
1 return@a 1
就是“在標簽@a處返回1”而不是“返回標簽表達式(@a 1)”。