Kotlin 知識梳理系列文章
Kotlin 知識梳理(1) - Kotlin 基礎(chǔ)
Kotlin 知識梳理(2) - 函數(shù)的定義與調(diào)用
Kotlin 知識梳理(3) - 類晶默、對象和接口
Kotlin 知識梳理(4) - 數(shù)據(jù)類、類委托 及 object 關(guān)鍵字
Kotlin 知識梳理(5) - lambda 表達(dá)式和成員引用
Kotlin 知識梳理(6) - Kotlin 的可空性
Kotlin 知識梳理(7) - Kotlin 的類型系統(tǒng)
Kotlin 知識梳理(8) - 運(yùn)算符重載及其他約定
Kotlin 知識梳理(9) - 委托屬性
Kotlin 知識梳理(10) - 高階函數(shù):Lambda 作為形參或返回值
Kotlin 知識梳理(11) - 內(nèi)聯(lián)函數(shù)
Kotlin 知識梳理(12) - 泛型類型參數(shù)
一畏邢、本文概要
本文是對<<Kotlin in Action>>
的學(xué)習(xí)筆記剂跟,如果需要運(yùn)行相應(yīng)的代碼可以訪問在線環(huán)境 try.kotlinlang.org减途,這部分的思維導(dǎo)圖為:
二、函數(shù)和變量
2.1 函數(shù)
2.1.1 函數(shù)的基本構(gòu)成
在Kotlin
中曹洽,函數(shù)的基本結(jié)構(gòu)由四個部分構(gòu)成:
- 函數(shù)名稱
- 參數(shù)列表
- 返回類型
- 函數(shù)體
函數(shù)的聲明以關(guān)鍵字 fun 開始鳍置,函數(shù)名稱 緊隨其后,接下來是括號括起來的 參數(shù)列表送淆,參數(shù)列表的后面跟著 返回類型墓捻,返回類型和參數(shù)列表之間用冒號隔開,最后是函數(shù)體坊夫。
下面是一個比較大小的函數(shù)例子砖第,上面談到的四個部分構(gòu)成如圖中標(biāo)注所示:
2.1.2 表達(dá)式和語句
在上面的例子中,if
是表達(dá)式环凿,而不是語句梧兼,表達(dá)式和語句的區(qū)別在于:
- 表達(dá)式 有值,并且能作為另一個表達(dá)式的一部分使用智听。
- 語句 總是包含著它的代碼塊中的頂層元素羽杰,并且沒有自己的值渡紫。
在Java
中,所有的控制結(jié)構(gòu)都是語句考赛,而在Kotlin
中惕澎,除了for
、do
和do/while
以外大多數(shù)控制結(jié)構(gòu)都是表達(dá)式颜骤。
當(dāng)函數(shù)體是由單個表達(dá)式構(gòu)成時唧喉,可以用這個表達(dá)式作為完整的函數(shù)體,并且去掉花括號和return
語句忍抽,上面的例子就是這種情況八孝,因此可以改寫為:
- 如果函數(shù)體寫在花括號中,我們說這個函數(shù)有 代碼塊體
- 如果它直接返回了一個表達(dá)式鸠项,它就有 表達(dá)式體
2.1.3 省略返回類型
對于 表達(dá)式體函數(shù)干跛,可以省略返回類型,因為編譯器會分析作為函數(shù)體的表達(dá)式祟绊,并把它的類型作為函數(shù)的返回類型楼入,這種分析稱為 類型推導(dǎo)。但是對于有返回值的 代碼塊體函數(shù)牧抽,必須顯示地寫出返回類型和return
語句浅辙。
上面的例子可以簡化為:
2.2 變量
在Kotlin
中,變量的聲明以關(guān)鍵字val/var
開始阎姥,然后是變量名稱记舆,最后可以加上類型(不加也可以),這里分為兩種情況:
- 如果指定了初始化器呼巴,那么在不指定類型的情況下泽腮,編譯器會分析初始化器表達(dá)式的值,并把它的類型作為變量的類型衣赶,例如下面兩個就分別為
Int
和Double
類型:
-
如果沒有指定初始化器诊赊,需要顯示地指定它的類型,因為此時編譯器無法推斷出它的類型府瞄。
2.2.1 可變變量和不可變變量
聲明變量的關(guān)鍵字有兩個:
(1) 不可變引用 val
使用val
聲明的變量不能在初始化之后再次賦值碧磅,它對應(yīng)的是Java
的final
變量。
默認(rèn)情況下遵馆,應(yīng)該盡可能地使用val
關(guān)鍵字來聲明所有的Kotlin
變量鲸郊。在定義了val
變量的代碼塊執(zhí)行期間,val
變量只能進(jìn)行唯一一次初始化货邓,但是秆撮,如果編譯器能確保唯一一條初始化語句會被執(zhí)行,可以根據(jù)條件使用不同的值來初始化它换况。
(2) 可變引用 var
這種變量的值可以改變职辨,但是它的類型卻是改變不了的盗蟆。
如果需要在變量中存儲不匹配類型的值,必須手動把值轉(zhuǎn)換或強(qiáng)制轉(zhuǎn)換到正確的類型舒裤。
2.2.2 字符串模板
-
Kotlin
可以在字符串字面值中引用局部變量喳资,只需要在變量名稱前面加上字符$
。
- 如果要在字符串中使用
$
腾供,需要對它進(jìn)行轉(zhuǎn)義仆邓。
運(yùn)行結(jié)果為:
-
除了可以引用局部變量之外,還可以引用更加復(fù)雜的表達(dá)式台腥,只需要把表達(dá)式用花括號擴(kuò)起來宏赘。
2.3 類
2.3.1 屬性
類的概念就是把數(shù)據(jù)和處理數(shù)據(jù)的代碼封裝成一個單一的實(shí)體绒北,在Java
中黎侈,數(shù)據(jù)存儲在字段中,并且通常是私有的闷游。如果想讓類的使用者訪問到數(shù)據(jù)得提供訪問方法峻汉,即getter/setter
。
在Java
中脐往,字段和其訪問器的組合常常被叫作屬性休吠。在Kotlin
中,屬性是頭等的語言特性业簿,完全 替代了字段和訪問器方法瘤礁。在類中聲明一個屬性和聲明一個變量一樣:使用val/var
關(guān)鍵字,前者是只讀的梅尤,而后者是可變的柜思。
當(dāng)聲明屬性的時候,就聲明了對應(yīng)的訪問器(只讀屬性有一個gettter
巷燥,而可變屬性則有getter/setter
)赡盘,例如下面的例子,聲明了只讀的name
屬性缰揪,可變的isMarried
屬性陨享,其賦值和讀取的方法如下所示:
運(yùn)行結(jié)果為:
2.3.2 自定義訪問器
假設(shè)聲明一個矩形,它能判斷自己是否是正方形钝腺,那么就不需要一個單獨(dú)的字段來存儲這個信息抛姑,此時我們可以寫一個自定義的訪問器:用val
開頭作為聲明,緊跟著的是屬性的名稱和類型艳狐,接下來是get()
關(guān)鍵字途戒,最后是一個函數(shù)體。
運(yùn)行結(jié)果為:
2.3.3 目錄和包
-
Kotlin
中包的概念和Java
類似僵驰,每個kotlin
文件都能以一個package
語句開頭喷斋,而文件中定義的所有聲明(類唁毒、函數(shù)及屬性)都會被放到這個包中。 - 如果其他文件定義的聲明也有相同的包星爪,這個文件可以直接使用它們哀卫;如果包不同往扔,則需要導(dǎo)入它們,導(dǎo)入語句放在文件的最前面并使用
import
關(guān)鍵字。 -
kotlin
不區(qū)分導(dǎo)入的是類還是函數(shù)鹏溯,而且,它允許使用import
關(guān)鍵字導(dǎo)入任何種類的聲明庄岖,可以直接導(dǎo)入頂層函數(shù)的名稱许蓖,也可以在包名稱后加上.*
來導(dǎo)入特定包中定義的所有聲明。 - 在
Java
中漓摩,要把類放到和包結(jié)構(gòu)相匹配的文件與目錄結(jié)構(gòu)中裙士,而在kotlin
中,可以把多個package
聲明不相同的類放在同一個文件夾中管毙。
2.4 表示和處理選擇:枚舉和 when
2.4.1 聲明枚舉類
簡單枚舉類
聲明枚舉類時腿椎,enum
是一個所謂的軟關(guān)鍵字,只有當(dāng)它出現(xiàn)在class
前面時才有特殊的意義夭咬,在其他地方可以當(dāng)做普通名稱使用啃炸。而class
仍然是一個關(guān)鍵字,下面是一個枚舉類的聲明:
帶屬性的枚舉類
以下是一個帶屬性的枚舉類:
運(yùn)行結(jié)果為:
當(dāng)聲明一個帶屬性的枚舉類時卓舵,有幾點(diǎn)需要注意:
- 當(dāng)聲明每個枚舉常量的時候南用,必須提供該常量的屬性值。
- 如果要在枚舉類中定義任何方法掏湾,就要使用分號把枚舉常量列表和方法分開裹虫。
2.4.2 使用 "when” 處理枚舉類
when
是一個有返回值的表達(dá)式,因此忘巧,作為表達(dá)式函數(shù)體恒界,它可以去掉花括號和return
語句,并省略返回類型的聲明砚嘴。
下面是一個通過when
處理枚舉類的例子十酣,它和Java
中的switch
語句類似,根據(jù)when
中Color
的值走到對應(yīng)的分支际长,除此之外耸采,我們可以把多個值用逗號間隔,合并到同一個分支:
運(yùn)行的結(jié)果為:
2.4.3 在 “when”結(jié)構(gòu)中使用任意對象
在Java
中工育,和when
類似的switch
語句要求必須使用常量(枚舉常量虾宇、字符串或者數(shù)字字面值)作為 分支條件,而when
允許使用任何對象如绸,我們使用一個函數(shù)來混合兩種顏色嘱朽。下面例子中用到的setOf
是由Kotlin
標(biāo)準(zhǔn)函數(shù)庫提供的旭贬,它可以創(chuàng)建出一個Set
,并且會包含所有指定為函數(shù)實(shí)參的對象搪泳,只要兩個set
中包含一樣的條目稀轨,它們就是相等的,集合的條目順序并不重要岸军。
運(yùn)行結(jié)果為:
除此之外奋刽,我們還可以不給
when
表達(dá)式提供參數(shù),這樣分支條件就是任意的布爾表達(dá)時艰赞,這種寫法的優(yōu)點(diǎn)是不會創(chuàng)建額外的對象佣谐,但代價是它更難理解。
2.4.4 智能轉(zhuǎn)換:合并類型檢查和轉(zhuǎn)換
在kotlin
中方妖,判斷一個變量是否是某種類型需要使用is
關(guān)鍵字狭魂,它和Java
當(dāng)中的instanceOf
相似。
- 在
Java
中吁断,在檢查完后還需要顯示地加上類型轉(zhuǎn)換趁蕊。 - 在
kotlin
中坞生,如果你檢查過一個變量是某種類型仔役,后面就不需要再轉(zhuǎn)換它,可以把它當(dāng)做你檢查過的類型來使用是己。
我們用下面這個例子又兵,Num
和Sum
都實(shí)現(xiàn)了Expr
接口,通過is
判斷它的類型卒废,完成遞歸求和沛厨。可以看到摔认,在is
判斷之后逆皮,不再需要轉(zhuǎn)換成Num
或Sum
,就可以直接訪問該類的成員變量参袱。
運(yùn)行結(jié)果為:
智能轉(zhuǎn)換 只在變量經(jīng)過is
檢查且之后不再發(fā)生變化 的情況下有效电谣,當(dāng)你對一個類的屬性進(jìn)行智能轉(zhuǎn)換的時候,這個屬性必須是一個val
屬性抹蚀,而且不能有自定義的訪問器剿牺,否則,每次對屬性的訪問是否都能返回同樣的值將無從驗證环壤。
2.4.5 代碼塊作為 "if" 和 "when" 的分支
if
和when
都可以使用代碼塊作為分支體晒来,這種情況下,代碼塊中的最后一個表達(dá)式就是結(jié)果郑现,這個規(guī)則在所有使用代碼塊并期望得到一個結(jié)果的地方成立湃崩。同樣的規(guī)則對try
主體和catch
子句也有效荧降。
運(yùn)行結(jié)果為:
2.5 迭代事物
2.5.1 while 循環(huán)
kotlin
和Java
一樣,有while
循環(huán)和do-while
循環(huán)攒读,它們的語法和Java
中相應(yīng)的循環(huán)完全一致誊抛。
2.5.2 迭代數(shù)字:區(qū)間和數(shù)列
在Java
當(dāng)中,對于循環(huán)的處理方式為:先初始化變量整陌,在循環(huán)的每一步更新它的值拗窃,并在值滿足某個限制條件時退出循環(huán)。
而在Kotlin
中泌辫,為了替代常見的循環(huán)用法随夸,使用了 區(qū)間 的概念,其本質(zhì)上就是兩個值之間的間隔震放,這兩個值通常是數(shù)字:一個起始值宾毒,一個結(jié)束值。使用..
運(yùn)算符來表示區(qū)間殿遂,而結(jié)束值始終是區(qū)間的一部分诈铛。
運(yùn)行結(jié)果為:
除此之外,還有
downTo
墨礁、step
和until
等用于區(qū)間的語法幢竹,用于進(jìn)行循環(huán)操作,例如下面的例子downTo
用于遞減到指定的值恩静,而step
則指定步長:運(yùn)行結(jié)果為:
使用
until
則可以使迭代不包含指定的結(jié)束值焕毫,例如下面這樣:運(yùn)行結(jié)果為:
2.5.3 迭代 map
更新 map
這里我們用到了TreeMap
,在更新map
時驶乾,我們可以像使用數(shù)組一樣邑飒,只不過下標(biāo)變成了key
值:
訪問 map
下面的例子展示了for
允許允許展開迭代中的集合的元素,把展開的結(jié)果存儲到了兩個獨(dú)立的變量中:letter
是鍵级乐、binary
是值:
運(yùn)行結(jié)果為:
2.5.4 使用 "in" 檢查集合和區(qū)間的成員
使用in
運(yùn)算符來檢查一個值是否在區(qū)間中疙咸,或者它的逆運(yùn)算!in
來檢查這個值是否不在區(qū)間中,區(qū)間不僅限于字符风科,假如有一個支持實(shí)例比較操作的任意類(實(shí)現(xiàn)了java.lang.Comparable
接口)撒轮,就能創(chuàng)建這種類型的對象的區(qū)間。
運(yùn)行結(jié)果為:
2.5 kotlin 中的異常
kotlin
的異常處理和Java
以及其他許多語言的處理方式類似:一個函數(shù)可以正常結(jié)束丐重,也可以在出現(xiàn)錯誤的情況下拋出異常腔召。方法的調(diào)用者能捕獲到這個異常并處理它;如果沒有處理扮惦,異常會沿著調(diào)用棧再次拋出臀蛛。
- 拋出異常時使用
throw
關(guān)鍵字,但是不必使用new
關(guān)鍵字來創(chuàng)建異常實(shí)例。 -
throw
結(jié)構(gòu)是一個表達(dá)式浊仆,能作為另一個表達(dá)式的一部分使用客峭。
2.5.1 “try” "catch" 和 "finally"
當(dāng)使用帶有catch
和finally
子句的try
結(jié)構(gòu)來處理異常時,下面是一個典型的結(jié)構(gòu):
- 和
Java
最大區(qū)別就是throws
子句沒有出現(xiàn)在代碼中:如果使用Java
來寫這個函數(shù)抡柿,你會顯示地在函數(shù)聲明上面寫上throws IOException
舔琅。這是因為IOException
是一個受檢異常,在Java
中洲劣,這種異常必須顯示地處理备蚓,必須聲明你的函數(shù)能拋出所有的受檢異常。
-
kotlin
不區(qū)分受檢異常和未受檢異常囱稽,不必指定函數(shù)拋出的異常郊尝,而且可以處理也可以不處理異常。 - 與此同時战惊,
BufferReader.close
可能拋出需要處理的受檢異常流昏,如果關(guān)閉失敗,大多數(shù)程序不會采取什么有意義的行動吞获,所以捕獲來自close
方法的異常所需的代碼是多余的况凉。
2.5.2 “try”作為表達(dá)式
kotlin
中的try
關(guān)鍵字就像if
和when
一樣,引入了一個表達(dá)式各拷,可以把它的值賦給一個變量刁绒,并且需要用花括號把語句主體括起來。如果主體包含多個表達(dá)式撤逢,那么整個try
表達(dá)式的值就是最后一個表達(dá)式的值膛锭。
運(yùn)行結(jié)果為:
更多文章粮坞,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:http://www.reibang.com/p/fd82d18994ce
- 個人主頁:http://lizejun.cn
- 個人知識總結(jié)目錄:http://lizejun.cn/categories/