> For the complete documentation index, see [llms.txt](https://kuroclass.gitbook.io/kotlin/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://kuroclass.gitbook.io/kotlin/ch8/bao-zhang-cheng-shi-de-an-quan.md).

# 保障程式的安全！

工程師們的願望就是希望自己寫出來的程式碼Bug越少越好，我們的程式越不會出錯，就代表程式越“安全”。而正好！Kotlin是一個注重安全性的程式語言，nullable型態讓我們可以順利、安全的處理“null”而不會無緣無故就跑出NullPointerException。最後我們會讓大家了解怎麼樣處理錯誤資訊。

## 移除物件參考

一般來說當我們宣告了一個變數，並且給予初始值後，編譯器會自動判斷該變數的型態：

```kotlin
var name = "小黑"
```

看到上面的語句，我們可以知道name的型態應該會是“String”，並且因為name是可變的變數，所以我們也可以指定另一個不同的物件參考給他：

```kotlin
name = "小固"
```

那如果在某些時候這個變數不需要物件參考的時候，我們要怎麼把物件的參考移除呢？

## 讓工程師們又愛又恨的null

如果我們要移除變數的參考，我們可以使用null。

### null是什麼

null我們稱之為空值，空值不好理解，我們可以想像一下，變數就像是一支遙控器，變數參考的物件就是一台冷氣機。我們可以透過變數，操控參考的物件，就像是我們可以使用遙控器操控已經配對好的冷氣機一樣。

如果變數是空值，代表這個遙控器沒有配對到任何冷氣機。但是我們卻拿著什麼都沒有的遙控器在教室裡面亂按，這個時候編譯器就會覺得你很奇怪，他沒辦法理解你想要做什麼，所以就會拋出NullPointerException。

### 把變數設定成null

我們了解null之後就可以來試試看把變數設定成null了

```kotlin
name = null
```

可是當我們這樣做的時候卻發現編譯器出現了紅字，錯誤訊息顯示：“Null can not be a value of a non-null type String”。這是怎麼一回事？原來在Kotlin裡面，可以設定成null的變數需要有特別的宣告方式：

## nullable型態

當我們需要把變數設定成null的時候，我們需要特別告訴編譯器：「這個變數有可能會是null喔！要特別小心注意他，以免他出事」。就像一個班級裡的問題學生一樣被老師特別關照，所以我們會用“?”來表示這個被“特別關照”的變數：

```kotlin
var name: String? = "小黑"
name = null
```

看！這個時候我們就可以把null放進這個變數裡面了。那我們可以在什麼地方使用nullable型態呢？

### 定義變數跟屬性的時候

我們可以把任何變數宣告成nullable型態，這個我們上面的例子已經有示範到了。至於什麼是屬性呢？我們等到後面學到物件的時候就會一起介紹囉～

### 定義函數參數的時候

在定義函數的時候使用nullable型態的參數代表著這個參數可以傳入null，但是這並不代表我們可以不傳入對應的引數，傳入null引數跟預設引數是不一樣的兩個概念，千萬別搞混了～

### 定義函數回傳型態的時候

定義函數的回傳值也可以使用nullable型態，這意味著函數的回傳值“有可能”會是null

有沒有發現上面的狀況跟lambda函數可以使用的地方是一樣的啊？沒有錯！不管是lambda函數還是nullable型態，他們本質上都是在跟你說“**這個東西應該要是什麼**”，所以他們可以使用的地方自然會是一樣的。

## 如何使用nullable型態？

### 使用if判斷null值

現在我們宣告了一個nullable的變數，可以我們卻發現宣告出來的變數，沒辦法正常的使用，會出現紅字

<figure><img src="/files/xRzdRMd7MBihve25gSS0" alt=""><figcaption></figcaption></figure>

那是因為編譯器強迫我們，必須確認好這個變數不是null的時候，我們才可以正常的使用它。所以我們可以先檢查，確定變數是不是真的有東西，再使用變數：

```kotlin
var name: String? = "小黑"
if (name != null) {
    name.reversed()
}
```

而且我們也可以寫出更複雜的判斷式，只要我們能確定使用到變數的時候它一定是有東西的：

```kotlin
var name: String? = "小黑"
if (name != null && name.length < 3) {
    name.reversed()
}
```

但是判斷式並不是一個好方法，一來判斷式太多會很難閱讀，二來他沒辦法幫我們避免掉所有的問題，例如這樣的狀況：

```kotlin
var name: String? = "小黑"

fun main() {
    if (name != null) {
        name.reversed()
    }
}
```

> ‘這個例子不是跟前面的差不多嗎？為什麼前面的沒有問題，這裡就出問題了？’
>
> “因為這個例子的變數是宣告在main函數外面，所以並不是只有main函數可以使用他！我們可能在其他地方同時使用到這個函數。萬一在判斷完變數是不是null之後，到使用變數之前，這個變數在其他地方被設定成null了，我們的程式就會出問題了！這個我們稱之為競態條件(Race Condition)，之後我們會更詳細的探討這個問題”

### 使用安全呼叫(?.)

對於nullable型態的呼叫我們最常使用安全呼叫運算子(?.)，就像我們應該隨時警惕nullable物件一樣，安全呼叫運算子看起來就像是隨時隨地的對nullable型態的變數保持警惕的樣子，只要東西不是null他就會通過讓呼叫執行，如果檢查到東西是null，就會攔下來不讓他做事。所以前面的範例我們改成安全呼叫，這樣就不會有問題了！

```kotlin
var name: String? = "小黑"

fun main() {
    name?.reversed()
}
```

### 使用let的安全呼叫

有些事情並不是變數本身可以做的，有些事情是需要把變數當引數傳進方法裡面，可是如果方法不接受nullable型態的話要怎麼辦？我們可以看看下面的範例怎麼處理：

```kotlin
var name: String? = "小黑"

fun main() {
    name?.let { doSomething(it) }
}

fun doSomething(text: String) {
    // do something
}
```

使用“?.let”我們就可以在lambda函數裡面的參數中取用到變數的“非nullable型態”，至於如果變數是null的話，就不會進入到lambda函數裡面。

### 使用Elvis(貓王)運算子(?:)

如果我們需要使用nullable的值，然後在他是null的時候回傳一個預設值的時候，我們就可以使用Elvis運算子：

```kotlin
var name: String? = "小黑"

fun main() {
    val length = name?.length ?: -1
    println(length)
}
```

Elvis運算子會檢查左邊的值是不是null，如果不是會回傳值，如果是null就會回傳右邊的值。至於為什麼會被叫做Elvis(貓王)運算子，是因為他旋轉90度後長的有點像貓王(Elvis Aaron Presley)

### 非null斷言運算子(!!)

非null斷言運算子是這幾種方法裡面最直接暴力的手段，使用他就像是你直接跟編譯器說：「這個東西絕對不是null！你給我直接把他當成非null型態來看就對了！」基本上，我會建議除非你100%確定這個東西不會是null的時候才使用，絕對不要因為貪圖方便去使用它，不然我們就會失去Kotlin帶給我們的安全性了。而萬一我們使用了斷言運算子，結果卻是null的話，會拋出NullPointerException。

```kotlin
var name: String? = "小黑"

fun main() {
    val length = name?.length!!
    println(length)
}
```

## 例外

例外是程式在執行的時候跳出的警告，用來跟我們說“我出事了！出了什麼事，在哪裡出事”的機制。就像剛剛的範例如果我們把"小黑"改成null的話。我們會看到下面的執行結果：

<figure><img src="/files/dpnfoPatLWNQOSJtCglA" alt=""><figcaption></figcaption></figure>

這邊我們可以看到幾個關鍵的地方：

* NullPointerException：跟我們表示發生了什麼錯誤，錯誤的種類繁多，後面我們會跟大家介紹一些比較基本、常遇到的錯誤
* “at com.kuro.MainKt.main(Main.kt:7)”：表示錯誤的地方在哪裡，這一系列的“at......”構成的描述錯誤出在什麼地方的東西我們稱呼他為“stack trace”。很多時候他會不只一行，我們需要耐心的看過並且找到真正的問題點，並解決他

例外被拋出的時候該怎麼辦？我們可以......

* 放著不管他
* 捕捉例外並且處理他

我們已經知道放著不管會發生什麼事了，甚至在手機app裡面會發生更可怕的事情“閃退”。為了不要讓我們的程式或是app運作到一半就沒辦法繼續運作下去，我們需要知道怎麼捕捉例外並且處理他。

## 嘗試(try)危險，並捕捉(catch)他

首先我們先來看看一段危險的程式碼：

```kotlin
fun main() {
    print("請輸入數字：")
    val inputNumber = readln().toInt()
    println(inputNumber)
}
```

這段程式碼會捕捉我們輸入的文字，轉換成整數並且印出來。看去來好像沒有問題對吧！？可是問題就出在我們可以輸入不是整數的文字，讓他爆炸(磅嘣！)像這樣一段危險的程式碼，我們就會使用try/catch包起來處理：

```kotlin
fun main() {
    try {
        print("請輸入數字：")
        val inputNumber = readln().toInt()
        println(inputNumber)
    } catch (e: NumberFormatException) {
        println("你輸入的不是整數！！")
    }
}
```

只要是在try裡面拋出的錯誤，我們就會把他接起來，並且留到catch裡面處理。catch後面的“( )”代表著這個catch區塊會處理什麼類型的錯誤(因為有些時候程式可能會拋出不只一種錯誤)，如果我們需要處理多種錯誤，可以在後面增加更多catch區塊。

## 最後(finally)我們一定要做的事

如果我們有一段程式碼，是在不管try成功還是try失敗，我們都必須要執行的。那我們就可以把他寫在finally裡面

```kotlin
fun main() {
    try {
        print("請輸入數字：")
        val inputNumber = readln().toInt()
        println(inputNumber)
    } catch (e: NumberFormatException) {
        println("你輸入的不是整數！！")
    } finally {
        println("危機解除！")
    }
}
```
