這章將講述
- 聲明函數(shù)密末、變量、類、枚舉和屬性
- Kotlin的控制結(jié)構(gòu)
- 智能強(qiáng)轉(zhuǎn)
- 拋和處理異常
1 基本元素: 函數(shù)和變量
Kotlin的兩大元素:函數(shù)和變量楞捂。你將明白,你可以省略類型聲明趋厉,還有鼓勵(lì)你使用不變而不是可變的數(shù)據(jù)寨闹。
1.1 Hello, world!
讓我們以一個(gè)經(jīng)典的例子開(kāi)始:打印“Hello, world!”,在Kotlin中君账,你只需要一個(gè)函數(shù):
fun main(args: Array<String>) {
println("Hello, world!")
}
在這段簡(jiǎn)短的代碼中繁堡,我們可以觀察到什么該語(yǔ)言的什么部分和特點(diǎn)呢?請(qǐng)看下面的列表:
- fun關(guān)鍵詞用來(lái)聲明一個(gè)函數(shù)
- 參數(shù)類型現(xiàn)在參數(shù)名字的后面乡数。同樣適用于變量聲明
- 函數(shù)可以在文件的最上層中聲明椭蹄;你沒(méi)必要把它放到一個(gè)類中
- 數(shù)列僅僅是類。不像Java净赴,Kotlin沒(méi)有特定的聲明數(shù)組的語(yǔ)法绳矩。
- 用println,而不是System.out.println玖翅。Kotlin標(biāo)準(zhǔn)庫(kù)提供了很多標(biāo)準(zhǔn)Java庫(kù)函數(shù)的包裝翼馆,這有更簡(jiǎn)潔的語(yǔ)法。println就是其中之一烧栋。
- 在一行的最后省略了分號(hào)写妥,就像在其他的語(yǔ)言。
1.2 函數(shù)
如果函數(shù)有返回類型审姓,在函數(shù)參數(shù)后面加冒號(hào)和返回類型:
fun max(a: Int, b: Int): Int {
return if (a > b) a else b
}
println(max(1, 2)) //2
注意珍特,在Kotlin中if是個(gè)有返回值的表達(dá)式。類似于Java中的三目運(yùn)算符(a > b)? a : b
語(yǔ)句(statement)和表達(dá)式(expression)
在Kotlin中魔吐,if是個(gè)表達(dá)式扎筒,而不是一個(gè)語(yǔ)句。語(yǔ)句和表達(dá)式的區(qū)別在于酬姆,表達(dá)式是一個(gè)值嗜桌,可以被用作另外表達(dá)式的一部分;而語(yǔ)句總是一個(gè)包含它的代碼塊內(nèi)的頂層元素辞色,沒(méi)有自己的值骨宠。在Java中,所有的控制結(jié)構(gòu)都是語(yǔ)句,但是在Kotlin中层亿,大部分控制結(jié)構(gòu)桦卒,除了循環(huán)(for , do和do/while),是表達(dá)式匿又。聯(lián)合控制結(jié)構(gòu)和其他的表達(dá)式方灾,可以讓你簡(jiǎn)潔表達(dá)許多通常的模式。
另外一方面碌更,在Java中賦值是表達(dá)式裕偿,但是在Kotlin中變成了語(yǔ)句。這有效避免了比較和賦值之間的混淆痛单,這個(gè)混淆也是錯(cuò)誤的一個(gè)來(lái)源嘿棘。
表達(dá)式主體
進(jìn)一步可以簡(jiǎn)化前面函數(shù),因?yàn)楹瘮?shù)體只含有單個(gè)語(yǔ)句桦他,你可以用表達(dá)式來(lái)作為整個(gè)函數(shù)體蔫巩,移除花括號(hào)和返回語(yǔ)句:
fun max(a: Int, b: Int): Int = if (a > b) a else b
如果用花括號(hào)來(lái)表達(dá)函數(shù)主體,我們叫這個(gè)函數(shù)為代碼塊體(block body)快压,如果直接返回表達(dá)式圆仔,我們叫它為表達(dá)式體(expression body)。
INTELLIJ IDEA提示 IntelliJ IDEA提供了在兩種不同函數(shù)風(fēng)格“Convert to expression body”和 “Convert to block body”之間的轉(zhuǎn)換
表達(dá)式體的函數(shù)在Kotlin代碼中很常見(jiàn)蔫劣,這個(gè)風(fēng)格不止用在一行函數(shù)坪郭,也用在對(duì)單一和更加復(fù)雜的表達(dá)式求值,比如if脉幢,when和try歪沃。我們進(jìn)一步省略返回類型:
fun max(a: Int, b: Int) = if (a > b) a else b
為什么函數(shù)沒(méi)有返回類型的聲明呢?Kotlin不是一個(gè)靜態(tài)語(yǔ)言嫌松,要求每個(gè)表達(dá)式在編譯階段都有類型嗎沪曙?事實(shí)上,每個(gè)變量和每個(gè)表達(dá)式都有類型萎羔,每個(gè)函數(shù)也有返回類型液走。但是對(duì)于表達(dá)式體的函數(shù),編譯器可以分析作為函數(shù)體的表達(dá)式贾陷,用它的類型作為返回類型缘眶,即使沒(méi)有顯示的寫(xiě)出來(lái)。分析的這個(gè)類型通常叫類型推導(dǎo)(type inference)髓废。
注意巷懈,省略返回類型僅僅在表達(dá)試體的函數(shù)中允許。有代碼塊體的有返回值的函數(shù)慌洪,你必須指明返回類型和顯示的返回語(yǔ)句顶燕。這是個(gè)有意的抉擇凑保。實(shí)際中的函數(shù)通常非常長(zhǎng),可能包含很多返回語(yǔ)句割岛,有顯示的返回類型和語(yǔ)句可以幫助你快速的知道什么被返回愉适。
1.3 變量
在Java中犯助,你用類型聲明變量癣漆。但是在Kotlin中,你可以也可以不把類型放到變量名后面剂买。省略類型的聲明如下
val question = "The Ultimate Question of Life, the Universe, and Everything"
val answer = 42
或者你顯示的指明
val answer: Int = 42
如果你要浮點(diǎn)型的常量惠爽,可以推導(dǎo)為Double
val yearsToCompute = 7.5e6//7.5X10^6 = 7500000.0
可變和不可變量
- val(來(lái)源于value)--- 不變的引用。一旦聲明為val的量初始化后瞬哼,不能夠重新賦值婚肆。對(duì)應(yīng)于Java里面的final變量
- var(來(lái)源于variable)--- 可變的引用。變量的值可以改變坐慰。對(duì)應(yīng)于Java里面的正常的變量(非final)
通常较性,盡量聲明所有的變量為val關(guān)鍵詞。只有有需要的時(shí)候结胀,才變?yōu)関al赞咙。用不可變的引用、不可變的實(shí)例和函數(shù)糟港,沒(méi)有副作用攀操,使得你的代碼更像函數(shù)式的風(fēng)格。val變量只能在代碼塊中初始化有且僅有一次秸抚。但是可以根據(jù)不同的情況速和,用不同的值來(lái)初始化,如果編譯器能夠保證僅有一個(gè)初始化語(yǔ)句執(zhí)行:
val message: String
if (canPerformOperation()) {
message = "Success"
// ... perform the operation }
else {
message = "Failed"
}
注意剥汤,val引用自己是不可變的颠放,但是,他指向的實(shí)例是可以改變的吭敢。比如碰凶,下面的代碼是完全有效的:
val languages = arrayListOf("Java") //聲明不可變的引用
languages.add("Kotlin")//改變引用指向的實(shí)例
盡管var關(guān)鍵詞允許變量改變他的值,但是它的類型是確定的:
var answer = 42
answer = "no answer"http://編譯錯(cuò)誤:類型不匹配
如果你想在變量里面存儲(chǔ)一個(gè)不匹配的類型的值省有,你必須轉(zhuǎn)換或者協(xié)變這個(gè)值到正確的類型痒留。
1.4 更容易的字符串格式化:字符串模板
這個(gè)部分開(kāi)始的“Hello World”例子,我們進(jìn)一步這個(gè)慣例蠢沿,用Kotlin的方式伸头,通過(guò)名字來(lái)問(wèn)候。
fun main(args: Array<String>) {
//打印“Hello舷蟀,Kotlin”恤磷,如果輸入?yún)?shù)為Bob面哼,則打印“Hello,Bob”
val name = if (args.size > 0) args[0] else "Kotlin"
println("Hello, $name!")
}
這個(gè)例子引進(jìn)了一個(gè)功能叫字符串模板(string templates)扫步。和其他腳本語(yǔ)言一樣魔策,Kotlin允許在字符串字面量中,通過(guò)$字符放在變量名前面河胎,引用本地變量闯袒。這個(gè)同Java中的字符串連接("Hello, " + name + "!"), 但是更加緊湊和有效率(注:都是創(chuàng)建StringBuilder,添加常量部分和變量值游岳,Java虛擬機(jī)有優(yōu)化)政敢。
如果你引用一個(gè)不存在的本地變量,因?yàn)楸磉_(dá)式會(huì)靜態(tài)檢查胚迫,這些代碼會(huì)編譯不成功喷户。如果你想在字符串中包含$符號(hào),用println("\$x")換碼访锻,打印出$x褪尝,而不是把x翻譯為一個(gè)變量的引用。
不限于一個(gè)簡(jiǎn)單的變量名期犬,你也可以用更加復(fù)雜的表達(dá)式河哑,僅僅只要在表達(dá)式括上花括號(hào):
fun main(args: Array<String>) {
//用${}插入args數(shù)組的第一個(gè)元素
if (args.size > 0) { println("Hello, ${args[0]}!") }
}
你也可以雙引號(hào)內(nèi)陷雙引號(hào),只要他們是在同一個(gè)表達(dá)式:
fun main(args: Array<String>) {
println("Hello, ${if (args.size > 0) args[0] else "someone"}!")
}
2 類和屬性
讓我們看看一個(gè)簡(jiǎn)單的JavaBean的Person類哭懈,現(xiàn)在只包含一個(gè)name屬性
/* Java */ public class Person {
private final String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
在Java中灾馒,構(gòu)造子的代碼塊內(nèi),常常包含一些重復(fù)內(nèi)容:把參數(shù)賦值到響應(yīng)的域遣总。在Kotlin中睬罗,這個(gè)邏輯不需要如此多的樣板代碼。在第一章中5.6節(jié)旭斥,我們介紹了Java-to-Kotlin轉(zhuǎn)換器:一個(gè)自動(dòng)把Java代碼轉(zhuǎn)換到Kotlin代碼的工具容达,代碼的功能是相同的。
class Person(val name: String)
如果你熟悉現(xiàn)代的JVM語(yǔ)言垂券,你可能見(jiàn)過(guò)類似的東西花盐。這個(gè)類型的類(只包含數(shù)據(jù),但沒(méi)有代碼)菇爪,常常叫值實(shí)例(value object)算芯,許多語(yǔ)言提供了聲明他們的簡(jiǎn)潔語(yǔ)法。注意到在轉(zhuǎn)換過(guò)程中凳宙,public修飾符不見(jiàn)了熙揍。因?yàn)樵贙otlin,public是默認(rèn)的可見(jiàn)性氏涩,所以你能省略它
2.1 屬性
你肯定知道届囚,類的概念是封裝數(shù)據(jù)和處理數(shù)據(jù)的代碼到單一的實(shí)體有梆。在Java中,數(shù)據(jù)存儲(chǔ)到域中意系,通常是私有的泥耀。如果你想讓類的客戶端訪問(wèn)這個(gè)數(shù)據(jù),你需要提供訪問(wèn)器方法(accessor meth-ods):一個(gè)getter蛔添、可能有一個(gè)setter痰催。你在Person類的例子中已經(jīng)看到。setter可能包含一些額外的邏輯作郭,驗(yàn)證傳遞值陨囊,或者發(fā)送值變化的通知等等。
在Java中夹攒,域和訪問(wèn)器的組合,通常叫做屬性(property)胁塞, 很多框架較多使用這個(gè)概念咏尝。在Kotlin中,屬性是語(yǔ)言支持的第一等功能啸罢,完全用來(lái)替代域和它的訪問(wèn)器方法乘瓤。就像你用val和var關(guān)鍵詞撬碟,定義一個(gè)變量,你可以同樣的方式定義類的屬性。聲明為val的屬性是只讀的暂雹,而var屬性是可變的,
class Person(
val name: String, //只讀屬性:自動(dòng)生成一個(gè)域和簡(jiǎn)單的getter
var isMarried: Boolean //可寫(xiě)屬性:一個(gè)域擂仍,getter和setter
)
基本上戴尸,當(dāng)你定一個(gè)屬性,你就定義了相應(yīng)的訪問(wèn)器琅捏。默認(rèn)地生百,定義訪問(wèn)器也是簡(jiǎn)單的,域存儲(chǔ)值柄延、getter和setter來(lái)返回和更新值蚀浆。如果你愿意,用不同的邏輯計(jì)算和更新屬性值搜吧,來(lái)自定義訪問(wèn)器市俊。上面的Person簡(jiǎn)潔的定義隱藏了實(shí)現(xiàn)的細(xì)節(jié),就像原來(lái)的Java代碼一樣:一個(gè)類含有私有的域并且在構(gòu)造子中初始化滤奈,可以用響應(yīng)的getter獲取到摆昧。這意味著,你可以在Java和Kotlin中使用這個(gè)類僵刮,不管這個(gè)類在哪里申明的据忘。使用是一樣的鹦牛。下面是Java代碼中如何使用:
/* Java */
Person person = new Person("Bob", true);
System.out.println(person.getName()); //Bob
System.out.println(person.isMarried()); //true
Kotlin的name屬性在Java中的getter方法叫g(shù)etName。getter和setter命名規(guī)則有個(gè)例外:如果屬性名以is開(kāi)始勇吊,getter沒(méi)有附加的前綴曼追,在setter名字中,is被set取代汉规。所以礼殊,在Java中,你調(diào)用isMarried()针史。如下是Kotlin的結(jié)果
val person = Person("Bob", true)
println(person.name)// Bob
println(person.isMarried) //true
你不是調(diào)用getter晶伦,而是直接引用屬性。邏輯是一樣的啄枕,但是代碼更加簡(jiǎn)潔婚陪。可變屬性的setter一樣频祝,在java中你用person.setMarried(false)表達(dá)離婚泌参,在Kotlin中person.isMarried = false。
提示 你可以在Java定義的類中使用Kotlin的屬性語(yǔ)法常空。在Java類中的getter可以在Kotlin中val屬性獲取沽一,getter/setter可以通過(guò)var屬性獲取。比如漓糙,如果在Java類定義了setName和setName的方法铣缠,那么可以通過(guò)叫name的屬性獲取。如果類定義了isMarried和setMarried方法昆禽,相應(yīng)的Kotlin屬性叫isMarried蝗蛙。
大多數(shù)情況下,屬性有相應(yīng)的支持屬性为狸,即存儲(chǔ)屬性值歼郭。但是如果值是隨手計(jì)算的,比如從其他屬性計(jì)算辐棒,你可以用自定義的getter表達(dá)病曾。
2.2 自定義訪問(wèn)器
這個(gè)部分,你將看到怎么自定義實(shí)現(xiàn)一個(gè)屬性訪問(wèn)器漾根。假設(shè)你聲明了一個(gè)長(zhǎng)方形泰涂,它可以告訴是不是一個(gè)正方形。你沒(méi)必要用單獨(dú)的域存儲(chǔ)這個(gè)信息辐怕,因?yàn)槟阈枰獎(jiǎng)討B(tài)檢查高是否等于寬:
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() { //Property getter declaration
return height == width
}
}
isSquere屬性不需要一個(gè)域來(lái)存儲(chǔ)它的值逼蒙。它僅僅只有自定義實(shí)現(xiàn)的getter。這個(gè)屬性被獲取時(shí)每次計(jì)算寄疏。注意到是牢,你不需要用花括號(hào)這個(gè)完整的語(yǔ)法僵井,你可有寫(xiě)成get() = height == width。這樣的屬性的調(diào)用也是一樣的:
val rectangle = Rectangle(41, 43)
println(rectangle.isSquare) //false
如果你想在Java中獲取這個(gè)屬性驳棱,你可以就像以前一樣調(diào)用isSquare方法批什。
你可能問(wèn),是否定義一個(gè)沒(méi)有參數(shù)的函數(shù)比自定義getter的屬性好社搅。這兩個(gè)選項(xiàng)是相似的:在實(shí)現(xiàn)和性能是沒(méi)有區(qū)別的驻债,它們僅僅在可讀性上有差別。一般講形葬,如果你描述一個(gè)類的特點(diǎn)(屬性)合呐,你應(yīng)該定義它為屬性。
2.3 Kotlin源碼布局:目錄和包
Java把所有的類放進(jìn)包里面笙以。Kotlin也像Java淌实,有包的概念。每個(gè)Kotlin文件在開(kāi)頭有package語(yǔ)句源织,文件中所有的聲明(類翩伪、函數(shù)和屬性)將放在這個(gè)包下。如果其他的文件在同一包下谈息,里面所有的定義可以直接使用;如果這些定義在不同包里面凛剥,那么他們需要導(dǎo)入侠仇。就像在Java中,導(dǎo)入語(yǔ)句放置在文件的開(kāi)頭犁珠,使用import關(guān)鍵詞逻炊。下面是個(gè)例子,展示包聲明和導(dǎo)入語(yǔ)句:
package geometry.shapes //包聲明
import java.util.Random //導(dǎo)入標(biāo)準(zhǔn)Java庫(kù)類
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() = height == width
}
fun createRandomRectangle(): Rectangle {
val random = Random()
return Rectangle(random.nextInt(), random.nextInt())
}
Kotlin不會(huì)區(qū)別導(dǎo)入類和函數(shù)犁享,允許你用import關(guān)鍵詞導(dǎo)入任何聲明余素。你可以通過(guò)名字導(dǎo)入頂層函數(shù)
package geometry.example
import geometry.shapes.createRandomRectangle //通過(guò)名字導(dǎo)入函數(shù)
fun main(args: Array<String>) {
println(createRandomRectangle().isSquare)//非常不可能打印"true"
}
通過(guò)包名后面加上.*,你可以導(dǎo)入特定包里面定義的所有聲明炊昆。注意桨吊,星號(hào)導(dǎo)入(star import)不僅使得定義包里面的類可見(jiàn),而且使得頂層函數(shù)和屬性可見(jiàn)凤巨。在上面的例子中视乐,import geometry.shapes.* 代替顯示的導(dǎo)入,也使得使得代碼正常編譯敢茁。
在Java中佑淀,目錄結(jié)構(gòu)和包的層級(jí)是重復(fù)的。在Kotlin中你可以在同個(gè)文件中定義多個(gè)類彰檬。Kotlin也沒(méi)限制磁盤(pán)上源文件的結(jié)構(gòu)伸刃。你可以用目錄結(jié)構(gòu)來(lái)組織你的文件谎砾。比如,你可以在文件shapes.kt中定義geometry.shapes包的所有內(nèi)容捧颅,然后把這個(gè)文件放在geometry目錄下景图,沒(méi)有必要?jiǎng)?chuàng)建shapes文件夾。
但是隘道,在大多數(shù)情況下症歇,跟隨Java目錄結(jié)構(gòu)和根據(jù)包結(jié)構(gòu)把源碼組織成目錄,是最佳實(shí)踐谭梗。特別是Kotlin和Java混合的項(xiàng)目忘晤,堅(jiān)持這樣的結(jié)構(gòu)特別重要。因?yàn)檫@樣做可以讓你逐步遷移代碼激捏,而沒(méi)有引入意外的情況设塔。但是請(qǐng)你不要猶豫把多個(gè)類合成到同一個(gè)文件,特別是當(dāng)類很小的時(shí)候(在Kotlin中远舅,這些經(jīng)常存在)闰蛔。
3 選項(xiàng)的表述和處理:枚舉和“when”
在這一節(jié)中,我們將講述when結(jié)構(gòu)图柏。它可以被想成Java中的switch替代品序六,但是更加強(qiáng)大和更常使用。同時(shí)蚤吹,有一個(gè)在Kotlin中聲明枚舉的例子例诀,然后討論智能強(qiáng)轉(zhuǎn)的概念。
3.1 聲明枚舉類
讓我們加一些想象的明亮照片到這個(gè)嚴(yán)肅的書(shū)籍裁着,看看下面顏色的枚舉
enum class Color {
RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}
與Java的僅僅有關(guān)鍵詞enum相比繁涂,Kotlin聲明使用更多的關(guān)鍵詞,這是比較少見(jiàn)的二驰。在Kotlin中扔罪,enum就是所謂的軟關(guān)鍵詞(soft keyword):當(dāng)它放置在class關(guān)鍵詞之前,它才有特有的意義桶雀。但是你可以在其他的地方矿酵,把它當(dāng)成常規(guī)的名字使用。另外一方面背犯,class還是一個(gè)關(guān)鍵詞坏瘩,你可以用clazz和aClass來(lái)聲明變量。
就像在Java中漠魏,枚舉不是值的列表:你可以在枚舉類中聲明屬性和方法:
enum class Color(
val r: Int, val g: Int, val b: Int //聲明枚舉常量的屬性
) {
RED(255, 0, 0), //當(dāng)每個(gè)變量創(chuàng)建的時(shí)候倔矾,指定屬性值
ORANGE(255, 165, 0), //逗號(hào)是必須的
YELLOW(255, 255, 0),
GREEN(0, 255, 0),
BLUE(0, 0, 255),
INDIGO(75, 0, 130),
VIOLET(238, 130, 238);
fun rgb() = (r * 256 + g) * 256 + b//定義枚舉的方法
}
println(Color.BLUE.rgb())//255
枚舉常量就像你看見(jiàn)的正常的類一樣,有相同的構(gòu)造子和屬性聲明。當(dāng)你定義一個(gè)枚舉常量哪自,你需要為它提供屬性值丰包。這個(gè)例子中展示了Kotlin語(yǔ)法唯一需要分號(hào)的地方:在枚舉類中如果你定義任何方法,分號(hào)區(qū)分了枚舉常量列表和方法聲明壤巷。
3.2 用“when”來(lái)處理枚舉類
你記得孩子如何利用助記短語(yǔ)來(lái)記憶彩虹的顏色嗎邑彪?這就是一個(gè):“Richard Of York Gave Battle In Vain!”假設(shè)你需要一個(gè)函數(shù)來(lái)給你為每個(gè)顏色一個(gè)助記(你不想把這些信息存儲(chǔ)在枚舉里面)。在Java中胧华,你使用switch語(yǔ)句寄症,在Kotlin中,響應(yīng)的結(jié)構(gòu)是when矩动。
就像if一樣有巧,when也是一個(gè)返回值的表達(dá)式,所以你可以寫(xiě)一個(gè)函數(shù)悲没,它的表達(dá)式體直接返回when表達(dá)式篮迎。如下:
fun getMnemonic(color: Color) = //直接返回一個(gè)“when”的表達(dá)式
when (color) { //如果顏色等于枚舉常量,返回響應(yīng)的字符串
Color.RED -> "Richard"
Color.ORANGE -> "Of"
Color.YELLOW -> "York"
Color.GREEN -> "Gave"
Color.BLUE -> "Battle"
Color.INDIGO -> "In"
Color.VIOLET -> "Vain"
}
}
println(getMnemonic(Color.BLUE)) // Battle
不像Java示姿,你不需要為每個(gè)分支寫(xiě)break語(yǔ)句(缺少break是Java代碼中引入錯(cuò)誤的原因)甜橱。你可以在同個(gè)分支結(jié)合值,用逗號(hào)來(lái)分離:
fun getWarmth(color: Color) = when(color) {
Color.RED, Color.ORANGE, Color.YELLOW -> "warm"
Color.GREEN -> "neutral"
Color.BLUE, Color.INDIGO, Color.VIOLET -> "cold"
}
println(getWarmth(Color.ORANGE)) //warm
上面的例子用全名來(lái)使用枚舉常量栈戳,指定Color枚舉類名岂傲。你可以用導(dǎo)入常量來(lái)簡(jiǎn)化代碼:
import ch02.colors.Color //導(dǎo)入聲明在另外一個(gè)包的Color類
import ch02.colors.Color.*//用名字顯示導(dǎo)入枚舉常量
fun getWarmth(color: Color) = when(color) {
RED, ORANGE, YELLOW -> "warm" //用名字導(dǎo)入常量
GREEN -> "neutral"
BLUE, INDIGO, VIOLET -> "cold"
}
3.3 使用任意實(shí)例的“when”
Java中的switch,需要使用常量(枚舉常量子檀、字符串或者數(shù)字字面常量)作為分支條件譬胎,但是在Kotlin中,when允許任何的實(shí)例命锄。下面我們寫(xiě)一個(gè)混合兩者顏色的函數(shù),如果它們可以在這個(gè)小的調(diào)色板中能夠混合偏化。
fun mix(c1: Color, c2: Color) =
when (setOf(c1, c2)) {//when表達(dá)式的參數(shù)可以是任何實(shí)例脐恩,用來(lái)被分支條件檢查
setOf(RED, YELLOW) -> ORANGE//枚舉可以混合的顏色對(duì)
setOf(YELLOW, BLUE) -> GREEN
setOf(BLUE, VIOLET) -> INDIGO
else -> throw Exception("Dirty color")//執(zhí)行這個(gè),如果沒(méi)有分支可以匹配
}
println(mix(BLUE, YELLOW))//GREEN
Kotlin標(biāo)準(zhǔn)庫(kù)中含有一個(gè)setOf的函數(shù)侦讨,用來(lái)創(chuàng)建Set驶冒,包含參數(shù)指定的實(shí)例;一個(gè)set是一個(gè)集合韵卤,它的項(xiàng)的次序并不重要骗污。所以,如果setOf(c1, c2)和setOf(RED, YELLOW)是相等的沈条,那么意味著要不然c1是RED和c2是YELLOW需忿,或者相反。
3.4 用沒(méi)有參數(shù)的when
上面的例子有點(diǎn)效率低下,因?yàn)槊看文阏{(diào)用這個(gè)函數(shù)屋厘,它都會(huì)創(chuàng)建幾個(gè)Set實(shí)例涕烧,僅僅是用在檢查兩個(gè)顏色是否匹配另外兩個(gè)顏色。正常情況下汗洒,通常不是個(gè)問(wèn)題议纯。但是如果這個(gè)函數(shù)經(jīng)常被調(diào)用,那么為了避免GC溢谤,值得用另外一種方式來(lái)重寫(xiě)這個(gè)代碼瞻凤。你可以用不帶參數(shù)的when表達(dá)式完成。代碼雖然可讀性差一點(diǎn)世杀,但是這是為了達(dá)到更好性能付出的代價(jià)阀参。
fun mixOptimized(c1: Color, c2: Color) =
when {
(c1 == RED && c2 == YELLOW) || (c1 == YELLOW && c2 == RED) -> ORANGE
(c1 == YELLOW && c2 == BLUE) || (c1 == BLUE && c2 == YELLOW) -> GREEN
(c1 == BLUE && c2 == VIOLET) || (c1 == VIOLET && c2 == BLUE) -> INDIGO
else -> throw Exception("Dirty color")
}
println(mixOptimized(BLUE, YELLOW)) //GREEN
如果when表達(dá)式?jīng)]有參數(shù),它的分支可以是任何的布爾值玫坛。mixOptimized函數(shù)和上面的mix做相同的事情结笨。優(yōu)點(diǎn)是不需要?jiǎng)?chuàng)建任何額外的實(shí)例,但是代價(jià)是更難閱讀湿镀。
3.5 智能強(qiáng)轉(zhuǎn):結(jié)合類型檢查和強(qiáng)轉(zhuǎn)
作為整個(gè)部分的例子炕吸,寫(xiě)一個(gè)簡(jiǎn)單算術(shù)表達(dá)式的求值,比如(1+2)+4勉痴。其他的算術(shù)操作(減乘除)也可以用類似的方式實(shí)現(xiàn)赫模,你可以當(dāng)做一個(gè)練習(xí)。
第一蒸矛, 你怎么編碼這個(gè)表達(dá)式瀑罗?你可以在樹(shù)狀結(jié)構(gòu)中存儲(chǔ),每個(gè)節(jié)點(diǎn)是一個(gè)總數(shù)(Sum)或者一個(gè)數(shù)字(Num)雏掠。Num永遠(yuǎn)是葉子節(jié)點(diǎn)斩祭,而Sum節(jié)點(diǎn)有兩個(gè)作為sum操作的參數(shù)的子節(jié)點(diǎn)。以下列表顯示的是一個(gè)用來(lái)編碼表達(dá)式的類的簡(jiǎn)單結(jié)構(gòu):Expr的接口乡话,它的Num和Sum兩個(gè)實(shí)現(xiàn)類摧玫。需要注意的是,Expr沒(méi)有聲明任何方法绑青,僅僅是用作標(biāo)記接口诬像,提供相同類型的不同種類表達(dá)式。
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
Sum存儲(chǔ)了左邊和右邊Expr類型的參數(shù)的引用闸婴。在這個(gè)小例子中坏挠,它們可以使Num或者Sum。為了存儲(chǔ)表達(dá)式(1+2)+4邪乍,你可以穿件一個(gè)實(shí)例:Sum(Sum(Num(1), Num(2)), Num(4))降狠。如下圖展示的樹(shù)狀結(jié)構(gòu)
我們看看怎么計(jì)算一個(gè)表達(dá)式的值:
println (eval(Sum(Sum(Num(1), Num(2)), Num (4)))) //7
Expr接口有兩個(gè)實(shí)現(xiàn)对竣,所以對(duì)于一個(gè)表達(dá)式求最終的值,你可以有兩種選擇:
- 如果表達(dá)式是一個(gè)數(shù)字喊熟,返回相應(yīng)的值
- 如果是一個(gè)和柏肪,對(duì)左邊和右邊表達(dá)式求值,返回它們的和
首先芥牌,我們用通常的Java方式來(lái)寫(xiě)這個(gè)函數(shù)烦味,然后用kotlin的方式重構(gòu)。在Java中壁拉,你可能用if語(yǔ)句序列來(lái)檢查選項(xiàng)谬俄,所以讓我們?cè)贙otlin中用同樣的方法:
fun eval(e: Expr): Int {
if (e is Num) {
val n = e as Num //顯式的強(qiáng)轉(zhuǎn)到Num是冗余的
return n.value
} if (e is Sum) {
return eval(e.right) + eval(e.left) //變量e是智能強(qiáng)轉(zhuǎn)
}
throw IllegalArgumentException("Unknown expression")
}
println(eval(Sum(Sum(Num(1), Num(2)), Num(4)))) //7
在Kotlin中,檢查一個(gè)變量是不是某種類型用is檢查弃理。如果你在C#編程過(guò)溃论,這個(gè)概念會(huì)很熟悉。is檢查和Java中的instanceOf類似痘昌。但是在Java中钥勋,如果你已經(jīng)檢查了一個(gè)變量是否是某種類型,同時(shí)想取得這個(gè)類型的屬性辆苔,你需要在instanceOf檢查后面再加一個(gè)顯式的類型轉(zhuǎn)換算灸。初始的變量需要不只使用一次,通常需要把類型轉(zhuǎn)換后的變量存儲(chǔ)到單獨(dú)的變量中驻啤。在kotlin中菲驴,編輯器為你做了這些工作。如果你檢查變量為某種類型骑冗,你沒(méi)必要在后面再類型轉(zhuǎn)換赊瞬。你可以當(dāng)做你想檢查的類型來(lái)使用它。事實(shí)上贼涩,編譯器巧涧,編譯為我們類型轉(zhuǎn)換了,我們叫它只能智能類型轉(zhuǎn)換(smart cast)
在eval函數(shù)中遥倦,在你檢查這個(gè)變量e是否是Num類型后褒侧,編譯器解釋它為Num變量。然后你可以不需要顯式的類型轉(zhuǎn)換就可以取得Num的value屬性谊迄。同樣的情況適用于Sum的右邊和左邊的屬性: 在相應(yīng)的情形下你只需要寫(xiě)e.right和e.left。在IDE中烟央,智能轉(zhuǎn)換的值用一個(gè)背景色來(lái)強(qiáng)調(diào)统诺,所以你可以很容易知道這個(gè)值是預(yù)先檢查了的,如下:
智能轉(zhuǎn)換只有變量在is檢查后沒(méi)有被改變疑俭。當(dāng)你對(duì)一個(gè)類的屬性進(jìn)行智能轉(zhuǎn)換粮呢,屬性必須是val,而且不能有自定義的存取器。否則不能確定每次獲得這個(gè)屬性將獲得同樣的值啄寡。一個(gè)顯式轉(zhuǎn)換到特定類型用as關(guān)鍵詞來(lái)表達(dá):
val n = e as Num
3.7 代碼塊作為if和when的分支
if和when都可以用代碼塊作為分支豪硅。在這個(gè)例子中,代碼塊中最后最后一個(gè)表達(dá)式作為結(jié)果挺物。如果你想在例子函數(shù)中加日志懒浮,你可以在代碼塊中完成,并用最后一個(gè)值返回识藤。
fun evalWithLogging(e: Expr): Int =
when (e) {
is Num -> {
println("num: ${e.value}")
e.value //如果e是Num類型砚著,這是代碼塊最后一個(gè)表達(dá)式,并被返回
}
is Sum -> {
val left = evalWithLogging(e.left)
val right = evalWithLogging(e.right)
println("sum: $left + $right")
left + right//如果表達(dá)式被返回當(dāng)e是Sum類型
}
else -> throw IllegalArgumentException("Unknown expression")
}
println(evalWithLogging(Sum(Sum(Num(1), Num(2)), Num(4))))
//num: 1
//num: 2
//sum: 1 + 2
//num: 4
//sum: 3 + 4
//7
“代碼塊中最后一個(gè)表達(dá)式是返回值”這個(gè)規(guī)則痴昧,在使用代碼塊而且期待返回一個(gè)結(jié)果的情況西安稽穆,所有情形下都成立。你將在本章結(jié)束的時(shí)候赶撰,同樣的規(guī)則在try代碼體和catch子句下同樣適用舌镶。在第五章論述lambada表達(dá)式下它的應(yīng)用。但是在2.2節(jié)中提到豪娜,這個(gè)規(guī)則對(duì)于常規(guī)的函數(shù)式不適用的餐胀。一個(gè)函數(shù)可以是沒(méi)有代碼塊的表達(dá)式體,或者是一個(gè)有顯示的return語(yǔ)句的代碼塊體
4 迭代事物:while和for循環(huán)
for循壞只存在一種形式侵歇,相對(duì)于Java的for-each循環(huán)骂澄。就像C#里面的寫(xiě)法:for <item> in <elements>。不存在Java中的通常for語(yǔ)法惕虑。
4.1 while循環(huán)
和Java對(duì)應(yīng)的循環(huán)一樣的語(yǔ)法
while (condition) { //當(dāng)條件為真時(shí)坟冲,代碼體執(zhí)行
/*...*/
}
do {//無(wú)條件的執(zhí)行一次,之后當(dāng)條件為真時(shí)執(zhí)行
/*...*/
} while (condition)
4.2 數(shù)字的迭代:范圍和累進(jìn)
由于不存在Java中通常的for語(yǔ)法溃蔫,Kotlin用范圍(ranges)這個(gè)概念健提。范圍是兩個(gè)值之間的間距,這兩個(gè)值為開(kāi)始和結(jié)束的值伟叛,用..操作子表示私痹。
val oneToTen = 1..10
范圍在Kotlin是自閉的(Closed)或者自包含(inclusive),這意味著第二個(gè)值總是范圍的一部分统刮。如果你迭代范圍內(nèi)的所有的值紊遵,這樣的范圍也叫累進(jìn)(progression)。
讓我們用整數(shù)的范圍玩Fizz-Buzz游戲侥蒙。參與者輪流遞增式數(shù)數(shù)暗膜,用fizz單詞替代任何可以被三整除的數(shù)字,用buzz單詞替代任何可以被五整除的數(shù)字鞭衩。如果一個(gè)數(shù)字同時(shí)是三和五的乘數(shù)学搜,我們叫“FizzBuzz”娃善。
如下列表打印了從1到100之間的正確答案。注意這么用沒(méi)有參數(shù)的when表達(dá)式檢查可能的條件:
fun fizzBuzz(i: Int) = when {
i % 15 == 0 -> "FizzBuzz " //i可以被15整除瑞佩,返回FizzBuzz聚磺。就像在Java中,%是模操作
i % 3 == 0 -> "Fizz " //i可以被5整除炬丸,返回Buzz
i % 5 == 0 -> "Buzz " //i可以被3整除瘫寝,返回Fizz
else -> "$i " //Else返回這個(gè)數(shù)字本身
}
for (i in 1..100) { //迭代整數(shù)范圍1..100
print(fizzBuzz(i))
}
//1 2 Fizz 4 Buzz Fizz 7 ...
如果你覺(jué)得厭倦了這些規(guī)則,想要把規(guī)則搞的復(fù)制一些御雕。讓我們從100倒過(guò)來(lái)數(shù)矢沿,而且只包括偶數(shù):
for (i in 100 downTo 1 step 2) {
print(fizzBuzz(i))
}
//Buzz 98 Fizz 94 92 FizzBuzz 88 ...
當(dāng)你以一個(gè)步長(zhǎng)step迭代一個(gè)累進(jìn),這可以忽略一些數(shù)字酸纲。這個(gè)步長(zhǎng)可以是負(fù)數(shù)捣鲸,這樣的話累進(jìn)向后而不是向前。這這個(gè)例子中闽坡,100 downTo 1 是一個(gè)(步長(zhǎng)為-1)向后的累進(jìn)栽惶。然后步長(zhǎng)改變它的絕對(duì)值為2,同時(shí)保持方向(事實(shí)上是疾嗅,設(shè)置步長(zhǎng)為-2).
就像前面提到的外厂,..語(yǔ)法創(chuàng)建了一個(gè)包含終點(diǎn)(..右邊的值)的一個(gè)范圍。在許多情況下代承,迭代半自閉的范圍汁蝶,即不包含指定的終點(diǎn),這樣會(huì)更加方便论悴。為了創(chuàng)建這樣一個(gè)范圍掖棉,用until函數(shù)實(shí)現(xiàn)。比如膀估,for (x in 0 until size)循環(huán)等于for (x in 0..size-1)幔亥,但是它表達(dá)的意思更加清楚。
4.3 map的迭代
我們提到過(guò)察纯,追常見(jiàn)的情形是帕棉,for...in循環(huán)是迭代一個(gè)集合。這個(gè)是和Java是一樣的饼记,所以毋庸贅言香伴。下面我們看看怎么迭代一個(gè)map。
舉個(gè)例子具则,讓我看看一個(gè)小程序瞒窒,打印字符的二進(jìn)制表示。僅僅為了展示的目的乡洼,你將存儲(chǔ)二進(jìn)制表示到一個(gè)map之中崇裁。下面的代碼創(chuàng)建了一個(gè)map,用一些字母的二進(jìn)制填進(jìn)去束昵,然后打印map里面的內(nèi)容拔稳。
val binaryReps = TreeMap<Char, String>()//用TreeMap,所以鍵是排序的
for (c in 'A'..'F') { //用字符的范圍迭代從A到F的字符
val binary = Integer.toBinaryString(c.toInt()) //ASCII編碼轉(zhuǎn)換到二進(jìn)制
binaryReps[c] = binary//在map中用c鍵存儲(chǔ)值
}
for ((letter, binary) in binaryReps) { //迭代一個(gè)map锹雏,把鍵值對(duì)賦值到兩個(gè)變量
println("$letter = $binary")
}
..語(yǔ)法創(chuàng)建范圍不僅僅對(duì)數(shù)字適用巴比,也對(duì)字符適用。我們用它迭代所有的字符礁遵,從A到(包括)F轻绞。
上面顯示了,for循環(huán)讓你解構(gòu)迭代集合的元素佣耐,在這個(gè)例子中政勃,是map里面鍵值對(duì)的集合。解構(gòu)的結(jié)果存儲(chǔ)到兩個(gè)不同值中:letter接受鍵兼砖,而binary接受值奸远。另外一個(gè)有用的技巧是,用鍵獲取和更新一個(gè)map里面的值讽挟。不是調(diào)用get和put懒叛,你用map[key]讀值,而用map[key] = value設(shè)置耽梅。代碼binaryReps[c] = binary相當(dāng)于Java里面的binaryReps.put(c, binary)薛窥。輸出如下:
A = 1000001 B = 1000010 C = 1000011 D = 1000100 E = 1000101 F = 1000110
你可有用同樣的結(jié)構(gòu)語(yǔ)法迭代一個(gè)集合,同時(shí)記錄當(dāng)前項(xiàng)的索引眼姐。你不必手動(dòng)的創(chuàng)建一個(gè)獨(dú)立的存儲(chǔ)索引的變量诅迷。編碼打印如你所料,如下:
val list = arrayListOf("10", "11", "1001")
for ((index, element) in list.withIndex()) {
println("$index: $element")
}
//0: 10
//1: 11
//2: 1001
4.4 用in檢查集合和范圍的屬性
用in操作子檢查一個(gè)值是否在范圍里面妥凳,或者想法竟贯,用!in檢查是否一個(gè)值是否不在一個(gè)范圍里面。下面看看怎么用in檢查一個(gè)字符是否屬于字符范圍里面:
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'
fun isNotDigit(c: Char) = c !in '0'..'9'
println(isLetter('q')) //true
println(isNotDigit('x')) //true
背下地逝钥,沒(méi)有任何詭計(jì):檢查字符編碼是否在第一個(gè)和最后一個(gè)編碼之間的某個(gè)地方屑那。但是這個(gè)邏輯被隱藏在標(biāo)準(zhǔn)庫(kù)里面范圍類的實(shí)現(xiàn)里面。
c in 'a'..'z'//變換成a <= c && c <= z
in和!in操作子也可以在when表達(dá)式里面使用
fun recognize(c: Char) = when (c) {
in '0'..'9' -> "It's a digit!"
in 'a'..'z', in 'A'..'Z' -> "It's a letter!"
else -> "I don't know…"
}
println(recognize('8')) //It's a digit!
范圍也不限于字符艘款。如果你有任何支持比較實(shí)例的類(實(shí)現(xiàn)java.lang.Comparable接口)持际,你可以創(chuàng)建那種類型實(shí)例的范圍。如果你有這樣的范圍哗咆,你不能枚舉這個(gè)范圍的所有的實(shí)例蜘欲。想想這個(gè):比如,你能枚舉在“Java”和“Kotlin”之間的所有的字符串嗎晌柬?是的姥份,你不能郭脂。但是你依然可以用in操作子檢查另外實(shí)例是否屬于這個(gè)范圍:
println("Kotlin" in "Java".."Scala") //和“Java” <= “Kotlin” && “Kotlin” <= “Scala”一樣
//true
字符串在這里是按字母比較的,因?yàn)槟鞘荢tring類怎么實(shí)現(xiàn)Comparable接口的澈歉。同樣的in檢查對(duì)集合也適用:
println("Kotlin" in setOf("Java", "Scala")) //這個(gè)集沒(méi)有“Kotlin”字符串
//false
5 Kotlin中的Exception
Kotlin中的異常處理與Java或者其他語(yǔ)言中的處理方式相似展鸡。一個(gè)函數(shù)可以以正常方式結(jié)束,或者當(dāng)錯(cuò)誤發(fā)生的時(shí)候拋出異常。函數(shù)調(diào)用者捕獲這個(gè)異常并處理它埃难;如果沒(méi)有莹弊,異常重新在調(diào)用棧向上拋。
Kotlin中的異常處理語(yǔ)句的基本形式和Java是相似的涡尘。你可以以不足為奇的方式拋出一個(gè)異常:
if (percentage !in 0..100) {
throw IllegalArgumentException( "A percentage value must be between 0 and 100: $percentage")
}
就像其他的類忍弛,你不需要用new關(guān)鍵詞創(chuàng)建異常實(shí)例。不像Java考抄,在Kotlin中细疚,throw結(jié)構(gòu)是一個(gè)表達(dá)式,可以用作為其他表達(dá)式的一部分:
val percentage =
if (number in 0..100)
number
else
throw IllegalArgumentException( //“throw” 是一個(gè)表達(dá)式
"A percentage value must be between 0 and 100: $number")
5.1 try座泳、catch和finally
就像Java之中惠昔,可以用try結(jié)構(gòu),和catch和finally子句處理異常挑势。如下镇防,讀取指定文件的一行,嘗試解析為個(gè)數(shù)字潮饱,然后返回一個(gè)數(shù)字来氧,如果這行不是有效的數(shù)字,返回null香拉。
fun readNumber(reader: BufferedReader): Int? { //不必要顯式地指定需要這個(gè)函數(shù)拋出的異常
try {
val line = reader.readLine()
return Integer.parseInt(line)
} catch (e: NumberFormatException) { //異常的類型在右邊
return null
} finally { //finally就像在Java一樣的
reader.close()
}
}
val reader = BufferedReader(StringReader("239"))
println(readNumber(reader))
//239
在段代碼和Java最大的不同是不需要throws子句:如果你在Java中寫(xiě)這個(gè)函數(shù)啦扬,你必須顯式地在函數(shù)聲明后面寫(xiě)throws IOException。你必須這么做是因?yàn)镮OException是受檢查的異常checked exception凫碌。在Java中扑毡,一個(gè)異常必須顯式的處理。你不得不聲明函數(shù)可以拋的所有的受檢查異常盛险。如果你調(diào)用其他的函數(shù)瞄摊,你需要處理它的受檢查的異常,或者聲明你的函數(shù)拋出這些異常苦掘。
就像其他現(xiàn)代JVM語(yǔ)言换帜,Koltin不區(qū)別受檢查和不受檢查的異常。你需要指定一個(gè)函數(shù)拋出的異常鹤啡,你可以也可以不處理這些異常惯驼。這個(gè)設(shè)計(jì)決定是基于Java中使用受檢查異常的實(shí)踐。經(jīng)驗(yàn)表明,Java規(guī)則常常需要很多無(wú)意義的代碼從新拋出或者忽略異常祟牲,這個(gè)規(guī)則并不能一致地避免發(fā)生的錯(cuò)誤隙畜。
在上面的例子中,NumberFormatException是一個(gè)不受檢查的異常说贝。所以Java編譯器不會(huì)強(qiáng)迫你捕獲這個(gè)異常禾蚕,你可以很容易的看見(jiàn)運(yùn)行時(shí)的異常。這相當(dāng)令人遺憾狂丝,因?yàn)椴挥行У妮斎霐?shù)據(jù)是經(jīng)常的事情,應(yīng)該更優(yōu)雅的處理哗总。同時(shí)几颜,BufferedReader.close方法也能拋出一個(gè)IOException異常,這是個(gè)需要處理的受檢查的異常讯屈。如果關(guān)閉一個(gè)流失敗了蛋哭,大部分代碼不能采取任何有意義的行動(dòng),所以需要從close方法捕獲異常的代碼基本是樣板代碼涮母。
那么關(guān)于Java 7的try-with-resources怎么樣呢谆趾?Kotlin沒(méi)有對(duì)應(yīng)的特別的語(yǔ)法;它被處理成一個(gè)庫(kù)函數(shù)叛本。
5.2 try作為一個(gè)表達(dá)式
為了顯示Java和Kotlin直接一個(gè)重要區(qū)別沪蓬,讓我們稍微改變下這個(gè)例子。移除fianlly部分(因?yàn)槟阋呀?jīng)知道這個(gè)怎么工作)来候,然后加一些代碼打印從這個(gè)文件讀取的數(shù)字跷叉。
fun readNumber(reader: BufferedReader) {
val number = try {
Integer.parseInt(reader.readLine()) //成為try表達(dá)式的值
} catch (e: NumberFormatException) {
return
}
println(number)
}
val reader = BufferedReader(StringReader("not a number"))
readNumber(reader)//沒(méi)有打印任何數(shù)字
Kotlin中try關(guān)鍵詞,就像if和when营搅,引進(jìn)了一個(gè)表達(dá)式云挟,你可以把它的值賦值給一個(gè)變量。不像if转质,你一直需要把語(yǔ)句保函在花括號(hào)中园欣。就像其他語(yǔ)句,如果包涵多個(gè)表達(dá)式休蟹,try表達(dá)式的值是最后一個(gè)表達(dá)式的值沸枯。在這個(gè)例子中,在catch代碼塊中有return語(yǔ)句鸡挠,所以這個(gè)函數(shù)在catch代碼塊后不會(huì)再進(jìn)行辉饱。如果你想繼續(xù)這個(gè)執(zhí)行,catch語(yǔ)句也需要一個(gè)值拣展,這個(gè)值是最后表達(dá)式的值:
fun readNumber(reader: BufferedReader) {
val number = try {
Integer.parseInt(reader.readLine()) //沒(méi)有異常發(fā)生時(shí)使用這個(gè)值
} catch (e: NumberFormatException) {
null //異常發(fā)生時(shí)使用null值
}
println(number)
}
val reader = BufferedReader(StringReader("not a number"))
readNumber(reader)//異常被拋出彭沼,所以函數(shù)打印null
//null
這時(shí)候如果你不耐煩了,你可以用類似Java中的寫(xiě)法备埃,開(kāi)始在Kotlin中寫(xiě)代碼姓惑。當(dāng)你讀這本書(shū)的時(shí)候褐奴,你將繼續(xù)學(xué)習(xí)怎么改變你習(xí)慣的思考方式,使用這個(gè)新語(yǔ)言的全部功能