1. 程式人生 > >Kotlin官方參考整理——01.開始

Kotlin官方參考整理——01.開始

前言

這是前段時間學習Kotlin官方參考( / )時所做的筆記,相比官方參考,這些筆記:

  1. 為了方便根據筆記去查閱官方參考,筆記的整體結構與官方參考基本保持一致
  2. 對官方參考中一些零散分佈的知識點進行了整合
  3. 對官方參考中個別講的比較籠統的知識點做了詳細展開
  4. 將官方參考中一些比較晦澀的知識點,使用個人認為更容易理解的方式進行了闡述
  5. 筆記並未100%覆蓋官方參考中的內容

這份筆記基本涵蓋了Kotlin的大部分知識點,可以作為 Kotlin快速入門 或是 官方參考的導讀 來使用。

另外,因為個人也正處於學習Kotlin的過程中,因此筆記中的理解和論述難免會有錯誤,希望讀者朋友不吝指正,大家共同學習進步。

1開始

1.1 基本語法

1.1.1 定義包

package foo.bar

//kotlin中語句結尾不需要寫分號
var aaa = 1

fun baz() {

}

class Goo {

}
……

包的宣告應處於原始檔頂部。不同於Java,Kotlin中包名可以不用與原始檔所在的目錄結構對應,也就是說原始檔可以隨便移動而不用修改其包名。

Kotlin原始檔的結構與C++類似,即變數、函式、類可以同級(如上面程式碼所示),而Java中,變數和函式都在類中。

原始檔所有內容都包含在宣告的包內。所以上例中 baz() 的全名是 foo.bar.baz 、Goo 的全名是foo.bar.Goo。如果沒有指明包,該檔案的內容屬於無名字的預設包。

1.1.2 訪問許可權修飾符(又稱為可見性修飾符)

Kotlin中變數、函式、類有三種位置,分別是:

  • 頂層位置:即在原始檔中頂層
  • 類中
  • 區域性位置:即在程式碼塊中或函式中

Kotlin中有四種可見性修飾符,分別為public(預設)、internal、protected、private。可以修飾頂層位置和類中的變數、函式、類,不能修飾區域性位置的變數、函式、類。

頂層位置的可見性修飾符:

  • public:隨處可見,預設。
  • internal:本模組內可見。
  • protected:不可用於頂層位置的變數、函式、類。
  • private:本檔案內可見。

類中的可見性修飾符:

  • public:能見到類的任何人都可見。
  • internal:在本模組內,能見到類的任何人都可見。
  • protected:本類和子類中可見。
  • private:本類中可見。

關於可見性的應用,舉一個簡單直觀的例子:

這裡寫圖片描述

/*
 *檔案File1.kt
 */
package cn.szx.kotlindemo

//預設可見性為public
fun function1(){
    print("function1 in File1")
}

//---------------------------------------------------------------------------------------

/*
 *檔案File2.kt
 */
package cn.szx.kotlindemo

fun function2(){
    //File2中可以訪問File1中定義的函式function1()
    //並且因為File2和File1在同一個包內,因此訪問function1()時不用加包名字首
    function1()
}

//編譯報錯,不能在同一個包內再定義一個function1()了
//因為此處的function1()和File1中的function1(),其全名都是cn.szx.kotlindemo.function1()
//系統無法區分二者
//fun function1(){
//
//}

//function1(a:Int)與File1中的function1()形參列表不同,可以區分,因此可以定義
fun function1(a:Int){
    print("function1 in File2"+a)
}

//---------------------------------------------------------------------------------------

/*
 *檔案File3.kt
 */
package cn.szx.kotlindemo2

//import cn.szx.kotlindemo.function1

fun function3(){
    //File3和File1不在同一個包內,因此訪問function1()時需要加包名字首
    cn.szx.kotlindemo.function1()

    //使用“import cn.szx.kotlindemo.function1”將function1匯入後,則可以省略字首
    //“import cn.szx.kotlindemo.function1”代表將cn.szx.kotlindemo包內所有名為function1的函式都匯入了
    //function1()
}

//允許定義function1()。與File1中的function1()的包名字首不同,因此係統可以區分
fun function1(){
    print("function1 in File3")
}

1.1.3 定義函式

基本規則:使用fun關鍵字來定義函式,形參的定義格式為“形參名:形參型別”。

//帶有兩個 Int 引數、返回 Int 的函式
fun sum(a: Int, b: Int): Int {
    return a + b
}

//將表示式作為函式體、返回值型別⾃動推斷的函式
fun sum(a: Int, b: Int) = a + b

//沒有返回值的函式
fun printSum(a: Int, b: Int) {
    println("sum of $a and $b is ${a + b}")
}

1.1.4 定義變數

注意:abstract只能修飾成員位置(即類中)的變數和函式,不能修飾頂層位置和區域性位置的變數和函式。

定義變數的基本格式:

var a:Int = 1//可變變數
val b:Int = 2//不可變變數
var c = 3//根據等號右邊的值自動推斷c的型別
var d:Int//定義時不進行初始化,則Int不能省略

var定義的是可變變數,即一般的變數,val定義的是不可變變數,即符號常量,類似於Java中的final變數或者C++中的const變數。

此外,處於不同位置的變數,其初始化要求是不同的:

  • 頂層變數必須進行初始化。(除非其沒有幕後欄位,詳見《03類和物件1.md》->“屬性”)
  • 類中的變數要麼進行初始化(可以直接後接等號進行初始化,也可以在建構函式或初始化塊(init塊)中初始化),要麼宣告為abstract。(除非其沒有幕後欄位,詳見《03類和物件1.md》->“屬性”)
  • 區域性變數可以不初始化。
package cn.szx.kotlindemo

var a: Int = 1              //頂層變數,必須進行初始化

private class Test{
    //abstract var a:Int    //類中的變數,宣告為抽象
    var b: Int = 2          //類中的變數,初始化
}


private fun baz() {
    var a: Int              //區域性變數,可以不初始化
}

1.1.5 字串

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

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

Kotlin有兩種型別的字串字面值: 轉義字串和原生字串。

轉義字串支援轉義字元,很像Java中的字串:val s = "Hello, world!\n"

原生字串使用三個引號( “”” )分界符括起來,不支援轉義,任何字元都會被原樣展示(包括換行、空格、tab等):

val text = """
    for (c in "foo")
        print(c)
    """

字串模板

字串可以包含模板表示式,使用時會用模板表示式的值來替換掉模板表示式。模板表示式以美元符( $ )開頭,由一個簡單的名字構成,如:

val i = 10
val s = "i = $i" // s為"i = 10"

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

val s = "abc"
val str = "$s.length is ${s.length}" // str為"abc.length is 3"

轉義字串和原生字串都支援模板表示式,那如果我們想要的就是$怎麼辦呢——使用${'$'}

1.1.6 條件表示式

Java中的很多語句,在kotlin中都既是語句,也是表示式(即本身可以代表一個值,可以放在等號右邊)。例如if表示式:


//if的分支可以是程式碼塊,最後的表示式的值作為該塊的值
val max = if (a > b) {
    print("Choose a")
    a
} else {
    print("Choose b")
    b
}

//也可以像下面這樣,類似於Java中的?:表示式
val max = if (a > b) a else b

1.1.7 空安全

1.1.7.1 非空型別與可空型別

預設情況下,所有型別都是非空的,即我們告訴編譯系統,該型別變數的值不可能是null,因此我們也不能將null賦值給該型別的變數:

var a: String = null    //非空型別取值不能為null,編譯報錯
var b: String = "hello"
val l = b.length        //因為b是非空型別的,所以b.length必然是安全的,不可能報出NPE(Null Pointer Exception)

在型別關鍵字後加上,那麼就得到了一個可空型別,如String?

var b: String? = null   //可空型別,取值可以為空
val l = b.length        //編譯報錯,因為b是可空型別,這樣呼叫是不安全的

那麼我們想要獲得可空的b的length屬性該怎麼辦呢?有兩種方法:

(1) 先進行空檢測

val l = if (b != null) b.length else -1

//也支援更復雜(更智慧的檢測)
if (b != null && b.length > 0) {
    print("String of length ${b.length}")
} else {
    print("Empty string")
}

(2) 使用安全呼叫操作符“?.”

val l = b?.length

如果b非空,就返回b.length,否則返回null,這個表示式的型別是Int?。

安全呼叫操作符在鏈式呼叫中很有用,例如:

bob?.department?.head?.name

只要任何一個環節為null,那麼該鏈式呼叫就會返回null。

如果要只對非空值執行某個操作,可以將安全呼叫操作符與let一起使用:

val listWithNulls: List<String?> = listOf("A", null)
for (item in listWithNulls) {
    item?.let { println(it) }   //輸出A並忽略null
}

1.1.7.2 Elvis操作符

表示式1 ?: 表示式2

含義:如果表示式1不是null,那麼就返回表示式1的值,否則就返回表示式2的值。注意,當且僅當表示式1的值為null時,才會對錶達式2進行求值。

throw和return在Kotlin中也都是表示式,所以它們也可以用在elvis操作符右側。這可能會非常方便:

fun foo(node: Node): String? {
    val parent = node.getParent() ?: return null
    val name = node.getName() ?: throw IllegalArgumentException("name expected")
    ……
}

1.1.7.3 !!操作符

這是為NPE愛好者準備的:

var a: String? = null   //a可空
val l = a!!.length      //如果引用a為null,則丟擲NPE,如果不為null,則返回a.length

1.1.7.4 過濾集合中的非空元素

如果你有一個可空型別元素的集合,並且想要過濾得到非空元素,你可以使用filterNotNull來實現:

val nullableList: List<Int?> = listOf(1, 2, null, 4)
val intList: List<Int> = nullableList.filterNotNull()

1.1.8 型別檢測與轉換

1.1.8.1 型別檢測與智慧型別轉換

型別檢測使用is或!is操作符。在多數情況下,不需要在Kotlin中使用顯式轉換操作符,因為編譯器會跟蹤不可變值的is檢查,並在需要時自動進行型別轉換:

if (obj is String) {
    print(obj.length)   //obj被自動轉換為String
}

if (obj !is String) {
    print("Not a String")
}else {
    print(obj.length)   //obj被自動轉換為String
}

if (x !is String) return
print(x.length)         //x被轉換為字串,可見編譯器是十分智慧的

//||右側的x被自動轉換為字串
if (x !is String || x.length == 0) return
//&&右側的x被自動轉換為字串
if (x is String && x.length > 0) {
    print(x.length)
}

但是需要注意,當編譯器不能保證變數在檢查和使用之間不可改變時,智慧轉換不能用(詳見官方參考)。

1.1.8.2 型別轉換操作符as

val b:String = a as String

當無法轉換時會丟擲型別轉換異常。

1.1.8.3 安全的型別轉換操作符as?

val b:String? = a as? String

當無法轉換時不會丟擲型別轉換異常,而是返回null。

1.1.9 For迴圈

Java中有普通的for迴圈和foreach迴圈(也稱為高階for迴圈),但kotlin中不支援普通的for迴圈,即你不能像這樣使用for迴圈:for(var i=0;i<100;i++){}

kotlin中的for迴圈都是高階for迴圈。for迴圈可以對任何提供迭代器(iterator)的物件進行遍歷,語法如下:

for(item in collection){
    print(item)
}

例如:

val strs = listOf("1", "2", "3")//List集合
val ints = arrayOf(1, 2, 3)//陣列

for (item in strs) {
    print(item)
}
for (item in ints) {
    print(item)
}

/*
 * 也可以通過索引來訪問
 */
for (index in strs.indices) {
    print(strs[index])
}
for (index in ints.indices) {
    print(ints[index])
}

1.1.10 while迴圈

while迴圈和do…while迴圈與Java中的用法一樣

val items = listOf("apple", "banana", "kiwi")
var index = 0
while (index < items.size) {
    println("item at $index is ${items[index]}")
    index++
}

1.1.11 when

when即可以作為語句,也可以作為表示式,它的作用類似於C++和Java中的switch語句,不過使用起來更加靈活。

when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> {
        print("x is neither 1 nor 2")
    }
}

//0,1兩個分支執行一樣的操作
when (x) {
    0, 1 -> print("x == 0 or x == 1")
    else -> print("otherwise")
}

//我們可以用任意表達式(而不只是常量)作為分支條件
when (x) {
    parseInt(s) -> print("s encodes x")//當x等於parseInt(s)時,執行此分支
    else -> print("s does not encode x")
}

//我們也可以檢測一個值在(in)或者不在(!in)某個區間或者集合中
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)某個特定型別的值
val hasPrefix = when(x) {
    is String -> x.startsWith("prefix")//會將x智慧轉換為String型別
    else -> false
}

//when也可以用來取代if-else if鏈。如果不提供引數,所有的分支條件都是簡單的布林表示式,而當一個分支的條件為真時則執行該分支
when {
    x.isOdd() -> print("x is odd")
    x.isEven() -> print("x is even")
    else -> print("x is funny")
}

1.1.12 使用區間(range)

使用..操作符來定義一個區間,如1..10。任何可比較的型別都可以定義為一個區間。區間常與in和!in配合使用。

if (i in 1..10) { //檢測i是否在區間[1,10]之內
    println(i)
}

整型區間(IntRange、LongRange、CharRange )有一個額外的特性:它們可以迭代。

前面講for迴圈時提到過,kotlin中的for迴圈類似於Java中的高階for迴圈,那麼如果我們非要使用Java中普通for迴圈那樣的for迴圈呢,答案就是使用區間:當我們使用for迴圈來迭代區間時,編譯器會將其轉換為類似於Java中的普通for迴圈。

for (i in 1..4) print(i) //輸出“1234”

等價於Java中的:

for(int i=1;i<=4;i++){...}

使用downTo操作符來定義一個逆向的區間for (i in 4 downTo 1) print(i),輸出“4321”

指定迭代的步長

for (i in 1..4 step 2) print(i) // 輸出“13”
for (i in 4 downTo 1 step 2) print(i) // 輸出“42”

使用util操作符定義一個不含尾元素的區間

for (i in 1 until 10) { // i in [1, 10),排除了10
    println(i)
}

1.1.13 使用集合

對集合進行迭代:

for (item in items) {
    println(item)
}

使用in運算子來判斷集合內是否包含某例項:

when {
    "orange" in items -> println("juicy")
    "apple" in items -> println("apple is fine too")
}

使用lambda表示式來過濾(filter)和對映(map)集合:

fruits
    .filter { it.startsWith("a") }
    .sortedBy { it }
    .map { it.toUpperCase() }
    .forEach { println(it) }

1.2 習慣用法

詳見官方參考。建議暫時擱置,最後再看。

1.3 編碼規範

詳見官方參考。建議暫時擱置,最後再看。