1. 程式人生 > >Kotlin 學習筆記(一) 基本型別和基本語法

Kotlin 學習筆記(一) 基本型別和基本語法

Kotlin 被提升為 Android 開發一級語言, 雖然短時間內 Java 並不會被取代, 不過學習 Kotlin 還是宜早不宜遲, 做好迎接變化的準備。

Android Studio 3.0 開始官方支援了 Kotlin, 因此, 先下一個 3.0 Preview 版 吧。

當前最新的 Preview 版是 Canary4。

學習資料

基本型別

  • Char 不再是數字

  • 數字型別之間不能通過賦值進行隱式提升, 比如把 Int 賦值給 Long, 但可以通過運算推斷, 比如 Long b = 3L + 1 // Long + Int => Long, 或者顯式提升。

  • 不支援八進位制, Long 型別需要標記 L, 如 Long a = 12L

  • 浮點值預設推斷為 Double, Float 表示為 1f 或者 1F

  • 數字字面值可新增下劃線方便閱讀 val oneMillion = 1_000_000

  • 雖然在 Kotlin 中型別都是物件, 但數字是作為 jvm 原生型別儲存, 除非在可空型別(如 Int?)或泛型時使用, 他們會被裝箱。 裝箱後和 java 中的裝箱類相似,值相等 (==) 但是物件不一定相同 (===)

位運算子

這是完整的位運算列表(只用於 Int 和 Long):

shl(bits) – 有符號左移 (Java 的 <<)
shr(bits) – 有符號右移 (Java 的 >>)
ushr(bits) – 無符號右移 (Java 的 >>>)
and(bits) – 位與
or(bits) – 位或
xor(bits) – 位異或
inv() – 位非

val x = (1 shl 2) and 0x000FF000

布林

內建的布林運算有:

|| – 短路邏輯或
&& – 短路邏輯與
! - 邏輯非

沒有 &|

fun arrays(){

    /* 我們可以使用庫函式 arrayOf() 來建立一個數組並傳遞元素值給它,這樣 arrayOf(1, 2, 3) 建立了 array [1, 2, 3]。
    或者,庫函式 arrayOfNulls() 可以用於建立一個指定大小、元素都為空的陣列。*/

    val arrayOf = arrayOf(1, 2, 3)
    val arrayOfNulls = arrayOfNulls<Int>(5
) /* 該構造第一個引數是 size, 第二個引數是一個 lamb 函式, 入參 i 代表陣列 index * 返回給定 index 的每個元素初始值 */ val arr = Array(3,{i -> i }) for (i in arr) { println(i) } }

字串

字串用 String 型別表示。字串是不可變的。 字串的元素——字元可以使用索引運算子訪問: s[i]。 可以用 for 迴圈迭代字串:

for (c in str) {
println(c)
}

字串字面值

Kotlin 有兩種型別的字串字面值: 轉義字串可以有轉義字元,以及原生字串可以包含換行和任意文字。轉義字串很像 Java 字串:

val s = "Hello, world!\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()

預設 | 用作邊界字首,但你可以選擇其他字元並作為引數傳入,比如 trimMargin(“>”)。
字串模板

字串可以包含模板表示式 ,即一些小段程式碼,會求值並把結果合併到字串中。 模板表示式以美元符($)開頭,由一個簡單的名字構成:

val i = 10
val s = "i = $i" // 求值結果為 "i = 10"

或者用花括號擴起來的任意表達式:

val s = "abc"
val str = "$s.length is ${s.length}" // 求值結果為 "abc.length is 3"

原生字串和轉義字串內部都支援模板。 如果你需要在原生字串中表示字面值 $ 字元(它不支援反斜槓轉義),你可以用下列語法:

val price = """
${'$'}9.99
"""

基礎語法

  • 結尾不需要加分號

  • 方法通過 fun 關鍵字宣告

  • 函式可以作為引數傳遞, 支援 lambda

定義區域性變數

  • 變數有 2 種, var 代表可變變數, val 代表不可變變數, 賦值後不可再次賦值

  • 如果是直接賦值的寫法, 變數的型別可通過值自動推斷, 可以宣告也可以省略

  • 如果沒有初始賦值, 型別必須宣告

val a: Int = 1  // 立即賦值
val b = 2   // 自動推斷出 `Int` 型別
val c: Int  // 如果沒有初始值型別不能省略
c = 3       // 明確賦值

定義函式

/**
 * 兩個 Int 相加 返回 Int
 */
fun sum(a: Int, b: Int): Int {
    return a + b
}

/**
 * 自動推斷引數型別和返回結果的寫法
 */
fun sum1(a: Int, b: Int) = a + b

/**
 *
 * 標準寫法, 無返回值型別時, 返回值型別宣告 : Unit 可以省略
 *
 * fun printSum(a: Int, b: Int): Unit {
 *   println("sum of $a and $b is ${a + b}")
 * }
 */
fun printSum(a: Int, b: Int) {
    // 字串中可以通過'$'符號引用變數, ${} 作為函式
    println("sum of $a and $b is ${a + b}")
}

可空變數宣告

當一個變數或者返回值可能為空, 需要在宣告型別後面加上 ? 做可空宣告,如 var a: Int?。在明確賦值或者進行非空判斷之前, 變數 a 的型別為 Int? 而非 Int

可空型別不可以直接使用, 需要轉換為非空型別, 而這一步是編譯器通過程式碼自動判斷的。


fun parseInt(str: String): Int? {
    return str.toIntOrNull()
}

fun printProduct(arg1: String, arg2: String) {
    val x = parseInt(arg1)
    val y = parseInt(arg2)

    // 直接使用 `x * y` 可能會報錯,因為他們可能為 null
    if (x != null && y != null) {
        // 在空檢測後,x 和 y 會自動轉換為非空值(non-nullable)
        println(x * y)
    }
    else {
        println("either'$arg1'or'$arg2'is not a number")
    }    
}

型別檢測和自動型別轉換

is 運算子檢測一個表示式是否某型別的一個例項。 如果一個不可變的區域性變數或屬性已經判斷出為某型別,那麼檢測後的分支中可以直接當作該型別使用,無需顯式轉換:

fun getStringLength(obj: Any): Int? {
    if (obj is String) {
        // `obj` 在該條件分支內自動轉換成 `String`
        return obj.length
    }

    // 在離開型別檢測分支後,`obj` 仍然是 `Any` 型別
    return null
}

fun getStringLength(obj: Any): Int? {
    if (obj !is String) return null

    // `obj` 在這一分支自動轉換為 `String`
    return obj.length
}

fun getStringLength(obj: Any): Int? {
    // `obj` 在 `&&` 右邊自動轉換成 `String` 型別
    if (obj is String && obj.length> 0) {
        return obj.length
    }

    return null
}

控制流

  • 三元運算子被取消了, 因為 if 表示式可以直接返回值, 代替三元運算子的作用
/**
 * 使用條件表示式的推斷返回值型別寫法
 */
fun maxOf(a: Int, b: Int) = if (a > b) a else b
  • when 關鍵字代替並擴充套件了 switch 關鍵字, 它也可以直接返回值或者與 函式inis 等其他關鍵字配合, 擴充套件判斷功能。

IF

if 的分支可以是程式碼塊,最後的表示式作為該塊的值:

val max = if (a> b) {
    print("Choose a")
    a
} else {
    print("Choose b")
    b
}

when

每個 -> 代表一個分支, 如果很多分支需要用相同的方式處理,則可以把多個分支條件放在一起,用逗號分隔:

when (x) {
    0, 1 -> print("x == 0 or x == 1")
    else -> print("otherwise")
}

如果其他分支都不滿足條件將會求值 else 分支 (類似原來的 default)。 如果 when 作為一個表示式使用,則必須有 else 分支, 除非編譯器能夠檢測出所有的可能情況都已經覆蓋了。

fun hasPrefix(x: Any) = when(x) {
    is String -> x.startsWith("prefix")
    else -> false
}

分支的判斷條件種類

  • 常量

  • 任意表達式

when (x) {
    parseInt(s) -> print("s encodes x")
    else -> print("s does not encode x")
}
  • in 在某個區間中, 或者 !in 不在某個區間中, 區間是 java 沒有的概念, 參考資料

區間示例:

for (i in 1..100) { …… }      // 閉區間:包含 100
for (i in 1 until 100) { …… } // 半開區間:不包含 100
for (x in 2..10 step 2) { …… }// 每到第二步, 取值, 然後重新計步, 246810
for (x in 10 downTo 1) { …… } // 從 10 到 1 倒序迴圈
if (x in 1..10) { …… }        // 判斷是否處於區間內
when (x) {
    in 1..10 -> print("x is in the range")
    in validNumbers -> print("x is valid")
    !in 10..20 -> print("x is outside the range")
    else -> print("none of the above")
}
  • is 是某個型別, 或者 !is 不是某個型別
fun hasPrefix(x: Any) = when(x) {
    is String -> x.startsWith("prefix")
    else -> false
}

代替 if, else if

如果不提供引數,所有的分支條件都是簡單的布林表示式,而當一個分支的條件為真時則執行該分支:

when {
    x.isOdd() -> print("x is odd")
    x.isEven() -> print("x is even")
    else -> print("x is funny")
}

for

類似 Java 的增強 For, 使用迭代器, 對於陣列的 for 會自動編譯為使用索引的迴圈

for (item in collection) print(item)

如果你想要通過索引遍歷一個數組或者一個 list, 使用如下格式

for (i in array.indices) {
    print(array[i])
}

或者可以用庫函式 withIndex

for ((index, value) in array.withIndex()) {
    println("the element at $index is $value")
}

while

whiledo..while 照常使用

fun useWhile() {

    // while 正常使用

    var x = 3

    while (x-- > 0) {
        print(x)
    }

    fun retrieveData() = x++

    do {
        val y = retrieveData()
        print(y)
    } while (y < 3) // y 在此處可見
}