Kotlin 知識梳理(1) Kotlin 基礎

澤毛發表於2017-12-21

一、本文概要

本文是對<<Kotlin in Action>>的學習筆記,如果需要執行相應的程式碼可以訪問線上環境 try.kotlinlang.org,這部分的思維導圖為:

kotlin 基礎

二、函式和變數

2.1 函式

2.1.1 函式的基本構成

Kotlin中,函式的基本結構由四個部分構成:

  • 函式名稱
  • 引數列表
  • 返回型別
  • 函式體

函式的宣告以關鍵字 fun 開始,函式名稱 緊隨其後,接下來是括號括起來的 引數列表,引數列表的後面跟著 返回型別,返回型別和引數列表之間用冒號隔開,最後是函式體。

下面是一個比較大小的函式例子,上面談到的四個部分構成如圖中標註所示:

Kotlin 知識梳理(1)   Kotlin 基礎

2.1.2 表示式和語句

在上面的例子中,if是表示式,而不是語句,表示式和語句的區別在於:

  • 表示式 有值,並且能作為另一個表示式的一部分使用。
  • 語句 總是包含著它的程式碼塊中的頂層元素,並且沒有自己的值。

Java中,所有的控制結構都是語句,而在Kotlin中,除了fordodo/while以外大多數控制結構都是表示式。

當函式體是由單個表示式構成時,可以用這個表示式作為完整的函式體,並且去掉花括號和return語句,上面的例子就是這種情況,因此可以改寫為:

Kotlin 知識梳理(1)   Kotlin 基礎

  • 如果函式體寫在花括號中,我們說這個函式有 程式碼塊體
  • 如果它直接返回了一個表示式,它就有 表示式體

2.1.3 省略返回型別

對於 表示式體函式,可以省略返回型別,因為編譯器會分析作為函式體的表示式,並把它的型別作為函式的返回型別,這種分析稱為 型別推導。但是對於有返回值的 程式碼塊體函式,必須顯示地寫出返回型別和return語句。

上面的例子可以簡化為:

Kotlin 知識梳理(1)   Kotlin 基礎

2.2 變數

Kotlin中,變數的宣告以關鍵字val/var開始,然後是變數名稱,最後可以加上型別(不加也可以),這裡分為兩種情況:

  • 如果指定了初始化器,那麼在不指定型別的情況下,編譯器會分析初始化器表示式的值,並把它的型別作為變數的型別,例如下面兩個就分別為IntDouble型別:
    Kotlin 知識梳理(1)   Kotlin 基礎
  • 如果沒有指定初始化器,需要顯示地指定它的型別,因為此時編譯器無法推斷出它的型別。
    Kotlin 知識梳理(1)   Kotlin 基礎

2.2.1 可變變數和不可變變數

宣告變數的關鍵字有兩個:

(1) 不可變引用 val

使用val宣告的變數不能在初始化之後再次賦值,它對應的是Javafinal變數。 預設情況下,應該儘可能地使用val關鍵字來宣告所有的Kotlin變數。在定義了val變數的程式碼塊執行期間,val變數只能進行唯一一次初始化,但是,如果編譯器能確保唯一一條初始化語句會被執行,可以根據條件使用不同的值來初始化它。

Kotlin 知識梳理(1)   Kotlin 基礎

(2) 可變引用 var

這種變數的值可以改變,但是它的型別卻是改變不了的。

Kotlin 知識梳理(1)   Kotlin 基礎
如果需要在變數中儲存不匹配型別的值,必須手動把值轉換或強制轉換到正確的型別。

2.2.2 字串模板

  • Kotlin可以在字串字面值中引用區域性變數,只需要在變數名稱前面加上字元$
    Kotlin 知識梳理(1)   Kotlin 基礎
  • 如果要在字串中使用$,需要對它進行轉義。
    Kotlin 知識梳理(1)   Kotlin 基礎
    執行結果為:
    Kotlin 知識梳理(1)   Kotlin 基礎
  • 除了可以引用區域性變數之外,還可以引用更加複雜的表示式,只需要把表示式用花括號擴起來。
    Kotlin 知識梳理(1)   Kotlin 基礎

2.3 類

2.3.1 屬性

類的概念就是把資料和處理資料的程式碼封裝成一個單一的實體,在Java中,資料儲存在欄位中,並且通常是私有的。如果想讓類的使用者訪問到資料得提供訪問方法,即getter/setter

Java中,欄位和其訪問器的組合常常被叫作屬性。在Kotlin中,屬性是頭等的語言特性,完全 替代了欄位和訪問器方法。在類中宣告一個屬性和宣告一個變數一樣:使用val/var關鍵字,前者是隻讀的,而後者是可變的。

當宣告屬性的時候,就宣告瞭對應的訪問器(只讀屬性有一個gettter,而可變屬性則有getter/setter),例如下面的例子,宣告瞭只讀的name屬性,可變的isMarried屬性,其賦值和讀取的方法如下所示:

Kotlin 知識梳理(1)   Kotlin 基礎
執行結果為:
Kotlin 知識梳理(1)   Kotlin 基礎

2.3.2 自定義訪問器

假設宣告一個矩形,它能判斷自己是否是正方形,那麼就不需要一個單獨的欄位來儲存這個資訊,此時我們可以寫一個自定義的訪問器:用val開頭作為宣告,緊跟著的是屬性的名稱和型別,接下來是get()關鍵字,最後是一個函式體。

Kotlin 知識梳理(1)   Kotlin 基礎
執行結果為:
Kotlin 知識梳理(1)   Kotlin 基礎

2.3.3 目錄和包

  • Kotlin中包的概念和Java類似,每個kotlin檔案都能以一個package語句開頭,而檔案中定義的所有宣告(類、函式及屬性)都會被放到這個包中。
  • 如果其他檔案定義的宣告也有相同的包,這個檔案可以直接使用它們;如果包不同,則需要匯入它們,匯入語句放在檔案的最前面並使用import關鍵字。
  • kotlin不區分匯入的是類還是函式,而且,它允許使用import關鍵字匯入任何種類的宣告,可以直接匯入頂層函式的名稱,也可以在包名稱後加上.*來匯入特定包中定義的所有宣告。
  • Java中,要把類放到和包結構相匹配的檔案與目錄結構中,而在kotlin中,可以把多個package宣告不相同的類放在同一個資料夾中。

2.4 表示和處理選擇:列舉和 when

2.4.1 宣告列舉類

簡單列舉類

宣告列舉類時,enum是一個所謂的軟關鍵字,只有當它出現在class前面時才有特殊的意義,在其他地方可以當做普通名稱使用。而class仍然是一個關鍵字,下面是一個列舉類的宣告:

Kotlin 知識梳理(1)   Kotlin 基礎

帶屬性的列舉類

以下是一個帶屬性的列舉類:

Kotlin 知識梳理(1)   Kotlin 基礎
執行結果為:
Kotlin 知識梳理(1)   Kotlin 基礎
當宣告一個帶屬性的列舉類時,有幾點需要注意:

  • 當宣告每個列舉常量的時候,必須提供該常量的屬性值。
  • 如果要在列舉類中定義任何方法,就要使用分號把列舉常量列表和方法分開。

2.4.2 使用 "when” 處理列舉類

when是一個有返回值的表示式,因此,作為表示式函式體,它可以去掉花括號和return語句,並省略返回型別的宣告。

下面是一個通過when處理列舉類的例子,它和Java中的switch語句類似,根據whenColor的值走到對應的分支,除此之外,我們可以把多個值用逗號間隔,合併到同一個分支:

Kotlin 知識梳理(1)   Kotlin 基礎
執行的結果為:
Kotlin 知識梳理(1)   Kotlin 基礎

2.4.3 在 “when”結構中使用任意物件

Java中,和when類似的switch語句要求必須使用常量(列舉常量、字串或者數字字面值)作為 分支條件,而when允許使用任何物件,我們使用一個函式來混合兩種顏色。下面例子中用到的setOf是由Kotlin標準函式庫提供的,它可以建立出一個Set,並且會包含所有指定為函式實參的物件,只要兩個set中包含一樣的條目,它們就是相等的,集合的條目順序並不重要。

Kotlin 知識梳理(1)   Kotlin 基礎
執行結果為:
Kotlin 知識梳理(1)   Kotlin 基礎
除此之外,我們還可以不給when表示式提供引數,這樣分支條件就是任意的布林表達時,這種寫法的優點是不會建立額外的物件,但代價是它更難理解。

2.4.4 智慧轉換:合併型別檢查和轉換

kotlin中,判斷一個變數是否是某種型別需要使用is關鍵字,它和Java當中的instanceOf相似。

  • Java中,在檢查完後還需要顯示地加上型別轉換。
  • kotlin中,如果你檢查過一個變數是某種型別,後面就不需要再轉換它,可以把它當做你檢查過的型別來使用。

我們用下面這個例子,NumSum都實現了Expr介面,通過is判斷它的型別,完成遞迴求和。可以看到,在is判斷之後,不再需要轉換成NumSum,就可以直接訪問該類的成員變數。

Kotlin 知識梳理(1)   Kotlin 基礎
執行結果為:
Kotlin 知識梳理(1)   Kotlin 基礎

智慧轉換 只在變數經過is檢查且之後不再發生變化 的情況下有效,當你對一個類的屬性進行智慧轉換的時候,這個屬性必須是一個val屬性,而且不能有自定義的訪問器,否則,每次對屬性的訪問是否都能返回同樣的值將無從驗證。

2.4.5 程式碼塊作為 "if" 和 "when" 的分支

ifwhen都可以使用程式碼塊作為分支體,這種情況下,程式碼塊中的最後一個表示式就是結果,這個規則在所有使用程式碼塊並期望得到一個結果的地方成立。同樣的規則對try主體和catch子句也有效。

Kotlin 知識梳理(1)   Kotlin 基礎
執行結果為:
Kotlin 知識梳理(1)   Kotlin 基礎

2.5 迭代事物

2.5.1 while 迴圈

kotlinJava一樣,有while迴圈和do-while迴圈,它們的語法和Java中相應的迴圈完全一致。

2.5.2 迭代數字:區間和數列

Java當中,對於迴圈的處理方式為:先初始化變數,在迴圈的每一步更新它的值,並在值滿足某個限制條件時退出迴圈。

而在Kotlin中,為了替代常見的迴圈用法,使用了 區間 的概念,其本質上就是兩個值之間的間隔,這兩個值通常是數字:一個起始值,一個結束值。使用..運算子來表示區間,而結束值始終是區間的一部分。

Kotlin 知識梳理(1)   Kotlin 基礎
執行結果為:
Kotlin 知識梳理(1)   Kotlin 基礎
除此之外,還有downTostepuntil等用於區間的語法,用於進行迴圈操作,例如下面的例子downTo用於遞減到指定的值,而step則指定步長:
Kotlin 知識梳理(1)   Kotlin 基礎
執行結果為:
Kotlin 知識梳理(1)   Kotlin 基礎
使用until則可以使迭代不包含指定的結束值,例如下面這樣:
Kotlin 知識梳理(1)   Kotlin 基礎
執行結果為:
Kotlin 知識梳理(1)   Kotlin 基礎

2.5.3 迭代 map

更新 map

這裡我們用到了TreeMap,在更新map時,我們可以像使用陣列一樣,只不過下標變成了key值:

Kotlin 知識梳理(1)   Kotlin 基礎

訪問 map

下面的例子展示了for允許允許展開迭代中的集合的元素,把展開的結果儲存到了兩個獨立的變數中:letter是鍵、binary是值:

Kotlin 知識梳理(1)   Kotlin 基礎
執行結果為:
Kotlin 知識梳理(1)   Kotlin 基礎

2.5.4 使用 "in" 檢查集合和區間的成員

使用in運算子來檢查一個值是否在區間中,或者它的逆運算!in來檢查這個值是否不在區間中,區間不僅限於字元,假如有一個支援例項比較操作的任意類(實現了java.lang.Comparable介面),就能建立這種型別的物件的區間。

Kotlin 知識梳理(1)   Kotlin 基礎
執行結果為:
Kotlin 知識梳理(1)   Kotlin 基礎

2.5 kotlin 中的異常

kotlin的異常處理和Java以及其他許多語言的處理方式類似:一個函式可以正常結束,也可以在出現錯誤的情況下丟擲異常。方法的呼叫者能捕獲到這個異常並處理它;如果沒有處理,異常會沿著呼叫棧再次丟擲。

  • 丟擲異常時使用throw關鍵字,但是不必使用new關鍵字來建立異常例項。
  • throw結構是一個表示式,能作為另一個表示式的一部分使用。

2.5.1 “try” "catch" 和 "finally"

當使用帶有catchfinally子句的try結構來處理異常時,下面是一個典型的結構:

Kotlin 知識梳理(1)   Kotlin 基礎

  • Java最大區別就是throws子句沒有出現在程式碼中:如果使用Java來寫這個函式,你會顯示地在函式宣告上面寫上throws IOException。這是因為IOException是一個受檢異常,在Java中,這種異常必須顯示地處理,必須宣告你的函式能丟擲所有的受檢異常。
    Java 處理方式
  • kotlin不區分受檢異常和未受檢異常,不必指定函式丟擲的異常,而且可以處理也可以不處理異常。
  • 與此同時,BufferReader.close可能丟擲需要處理的受檢異常,如果關閉失敗,大多數程式不會採取什麼有意義的行動,所以捕獲來自close方法的異常所需的程式碼是多餘的。

2.5.2 “try”作為表示式

kotlin中的try關鍵字就像ifwhen一樣,引入了一個表示式,可以把它的值賦給一個變數,並且需要用花括號把語句主體括起來。如果主體包含多個表示式,那麼整個try表示式的值就是最後一個表示式的值。

Kotlin 知識梳理(1)   Kotlin 基礎
執行結果為:
Kotlin 知識梳理(1)   Kotlin 基礎


更多文章,歡迎訪問我的 Android 知識梳理系列:

相關文章