---
# System prepended metadata

title: 初探 Kotlin
tags: [Kotlin]

---

###### tags: `Kotlin`
# 初探 Kotlin
Kotlin 以俄羅斯聖彼得堡附近的一座島嶼命名，因為開發團隊大部分位於這座島
Kotlin 設計出來是希望能取代 Java，這個語言的目標有三個
第一是必須要是靜態型態、第二完全相容 Java、第三是易於學習與推理

==目標平台==
主要目標是提供一個更簡潔、更高效、更安全的 Java 替代品
適用於當今使用 Java 的任何環境，除 Java 外，Kotlin 可以被編譯成 JavaScript

使用 Kotlin 最常見的領域是:
- 建構伺服器端的程式碼(通常是網頁的後台)
- 建構 Android APP

:::info
Kotlin/JVM 可將 Kotlin 編譯成 Java 字節碼，也就是所謂的 Bytecode，而這種字節碼是專門為 JVM 所設計的，JVM 會將字節碼轉譯為可直接執行的指令，因此 Java 和 Kotlin 本質上是使用相同的語言進行溝通的，這也使得 Kotlin 可以取代 Java。
:::

:::info
Kotlin/JS 可將 Kotlin 生成 JavaScript，而目前正在開發 Kotlin/JS IR 編譯器 (處於 Beta)，這種編譯器利用了一種新方法，先轉換為Kotlin intermediate representation (IR)，隨後編譯為 JavaScript，而不是直接從 Kotlin 生成 JavaScript 代碼。這優化並改進以前編譯器中存在的痛點，例如生成的代碼大小（通過消除死代碼），以及 JavaScript 和 TypeScript 生態系統的互操作性。
> https://kotlinlang.org/docs/js-ir-compiler.html
:::

==靜態型態==
Kotlin 跟 Java 一樣是靜態型態的程式語言，這表示程式中的型態，在編譯時都是已知的
編譯器可以驗證你正在擷取的物件上存在的方法和屬性

而動態型態的語言，能儲存、回傳任何型態的變數與函式，並在執行時解析引用的方法和屬性
這大大提升靈活度，但缺點是編譯期間無法檢測拼寫錯誤，可能造成執行階段的錯誤

優點
- 效能：執行速度更快，因為不需要在執行時才確認與檢查
- 可靠性：編譯時就驗證程式的正確性，大幅降低執行時產生崩潰的可能性
- 可維護性：型別確定能提高可讀性，較易於閱讀與維護
- 工具支援：可靠的重構、精確的程式碼實作和其他 IDE 功能

有優點肯定也有缺點，但 Kotlin 支援<font color=tomato>型態推論</font>，所以靜態型態大部分的缺點都消失了
因為不需要明確的宣告型態，可以由上下文進行推論

此外，Kotlin 也支援<font color=tomato>可空型態、函式型態</font>

:::info
靜態型態指能在編譯時期確認與檢查型別，動態型態則是在執行時才確認與檢查型別
:::

==免費及開源==
編譯器、函式庫、其他相關工具都是開源的，而且可免費用於任何目的

## 函式
它最大的特色是 if 是一個帶有結果值的表示式，如同 Java 的三元運算子(Kotlin 中沒有)
```kotlin=
fun max(a: Int, b: Int): Int {
    return if (a > b) a else b
}
```

上述函式可以再簡化
```kotlin=
fun max(a: Int, b: Int): Int = if (a > b) a else b
```

如果函式用大括號寫成，可以說此函式有一個<font color=tomato>區塊主體(block body)</font>
如果它直接回傳一個表示式，則它有一個<font color=tomato>表示式主體(expression body)</font>

上述函式因為<font color=tomato>型態推論</font>，所以可以再簡化，直接省略回傳型態的宣告
```kotlin=
fun max(a: Int, b: Int) = if (a > b) a else b
```

## 變數
有兩種關鍵字可以宣告變數
- var：可變的參考，這個變數的值可以被改變
- val：不變的參考，在初始化後就不能重新指派，對應到 Java 的 final

:::info
預設情況下，Kotlin 宣告的所有變數都應盡量使用 val 關鍵字，僅在必要時將其更改為 var
:::

## 字串
在使用變數組合字串的時候，可以使用 $
```kotlin=
val age = 18
println("I am $age years ago")
```

$ 也可以放入表達式
```kotlin=
val age = 18
println("I am ${age + 1} years ago")
```

:::info
使用 $ 比起一般的組合方式效率更高、更緊湊，因為編譯後的程式碼會建立一個 
StringBuilder，並為其加入常數(字串)與變數($ 後方的變數)
:::

## 類別與屬性
==類別==
在 Java 中建立一個類別，如下
```java=
public class Person {
    private final String name;
    
    public Person(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
}
```

在 Kotlin 中會變成這樣
```kotlin=
class Person(val name: String)
```

可以看到建構式、get 方法都消失了，這些都被 Kotlin 省略
而 public 也消失了，因為 Kotlin 中，public 是預設的
這種只有資料，沒有包含程式碼的類別，被稱為<font color=tomato>值物件(Value Objects)</font>

==屬性==
在 Java 中，資料儲存通常是私有欄位，並給予<font color=tomato>存取器方法</font>，而欄位和其存取器的組合被稱為屬性
但在 Kotlin 中，宣告屬性的方式與宣告變數相同，只要宣告了屬性，就同時宣告了對應的存取器
```kotlin=
val person = Person("Joe")
println(person.name) //Joe
person.name = "Allen"
println(person.name) //Allen
```

如果不想使用 Kotlin 自動產生的存取器或是有其他需求，也可以客製化
以下的方式可以不儲存 isAdult 這個欄位，而是在 get 時，做出計算並回傳
這樣的屬性也被稱作<font color=tomato>計算屬性 (Computed Property)</font>
```kotlin=
class Person(val name: String, val age: Int) {
    val isAdult: Boolean
        get() {
            return age >= 18
        }
}
```

```kotlin=
val person = Person("Joe", 18)
println(person.isAdult) //true
```

:::success
重點整理：屬性 (Property) 的定義是 Field + Accessor 也就是欄位(狀態) + 存取器(方法)
:::

:::info
這種客製的 getter 屬性與一個沒有參數的函式，哪個比較好呢? 其實兩種都是相似的
實作與效能上沒有區別，只差在可讀性，如果要描述類別的特性，宣告成屬性會比較妥當
:::

> 補充: [屬性僅開放get不開放set的方式](https://stackoverflow.com/questions/33428957/kotlin-public-get-private-set-var)

## 列舉和 when
==列舉==
在 Kotlin 中，enum 是一個軟關鍵字(soft keyword)，他在 class 前有特殊含義
但是可以在其他地方作為常規名稱。列舉不是值列表，所以可以在列舉類別上宣告屬性和方法

宣告一個簡單的列舉類別
```kotlin=
enum class Color {
    RED, BLUE
}
```

宣告含有屬性、方法的列舉類別
```kotlin=
enum class Color(val r: Int, val g: Int, val b: Int) {
    //列舉常數，最後需加上分號區隔 fun
    RED(255, 0, 0), BLUE(0, 0, 255); 
    
    fun rgb() = (r * 256 + g) * 256 + b
}
```

```kotlin=
println(Color.BLUE.rgb()) //255
```

每一個列舉常數都是一個物件，所以可以定義自己的匿名類別，內部能有各自的方法
也可以覆寫基類的方法，此外每一個列舉常數都擁有兩個屬性：
- name：String 類型，可以取得它的名稱
- ordinal：Int 類型，它在列舉類別中宣告的順序，即為索引值

以下使用抽象的概念，讓列舉常數必須實作基類的方法，並打印 name、ordinal
```kotlin=
enum class Color(val r: Int, val g: Int, val b: Int) {
    RED(255, 0, 0) {
        //定義自己的匿名類別，並實作基類的方法
        override fun print() {
            println(rgb())
        }
    };
    
    fun rgb() = (r * 256 + g) * 256 + b
    
    abstract fun print()
}
```

```kotlin=
Color.RED.print() //16711680
println(Color.RED.name) //RED
println(Color.RED.ordinal) //0
```

其他常用的列舉方法：
- EnumClass.valueOf(value: String)：返回 EnumClass，用 String 取得 EnumClass 對應的常數
- EnumClass.values()：返回 Array，裡面放著 EnumClass 中的所有列舉常數

```kotlin=
//定義列舉類別
enum class Color { RED, GREEN, BLUE }

//自訂一個返回所有列舉常數名稱的方法
inline fun <reified T : Enum<T>> printAllValues() {
    println(enumValues<T>().joinToString { it.name })
}
```

```kotlin=
printAllValues<Color>() //RED, GREEN, BLUE
println(enumValueOf<Color>("RED")) //RED
    
println(Color.valueOf("GREEN")) //GREEN
println(Color.values()[0]) //RED
println(Color.values()[1]) //GREEN
```

:::info
enumValueOf、enumValues 功用跟 valueOf、values 相同
:::

==when==
when 取代 Java 中的 switch，以下使用 when 與列舉作搭配
```kotlin=
enum class Color { RED, GREEN, BLUE }

fun isFavoriteColor(color: Color) = 
    when (color) {
        Color.RED -> true
        Color.GREEN -> false
        Color.BLUE -> false
    }
```

```kotlin=
print(isFavoriteColor(Color.RED)) //true
```

以上的 isFavoriteColor 方法，可以用分支結合來簡化
```kotlin=
fun isFavoriteColor(color: Color) = 
    when (color) {
        Color.RED -> true
        Color.GREEN, Color.BLUE -> false
    }
```

when 比 switch 更強大的一點是它允許判斷任何物件，以下使用 set 物件
```kotlin=
enum class Color { RED, GREEN, BLUE, YELLOW, ORANGE }

fun mix(c1: Color, c2: Color) = 
    when (setOf(c1, c2)) {
        setOf(Color.RED, Color.YELLOW) -> Color.ORANGE
        setOf(Color.YELLOW, Color.BLUE) -> Color.GREEN
        else -> throw Exception("Dirty color")
    }
```

```kotlin=
print(mix(Color.YELLOW, Color.RED)) //ORANGE
```

:::info
set 是集合，最大的特色是內容沒有特定順序，且不重覆，只要包含的內容相同
那麼這個 set 的集合就是相等的，這樣的特性剛好符合混合顏色的功能(上述範例)
:::

上述的 mix 方法，每次都會建立很多的 set，效率相當差，要改善可以用不帶參數的 when
來完成效率較高的判斷，但是程式碼的可讀性會比先前還要差
```kotlin=
fun mix(c1: Color, c2: Color) = 
    when {
        (c1 == Color.RED && c2 == Color.YELLOW) || (c2 == Color.RED && c1 == Color.YELLOW) -> Color.ORANGE
        (c1 == Color.YELLOW && c2 == Color.BLUE) || (c2 == Color.YELLOW && c1 == Color.BLUE) -> Color.GREEN
        else -> throw Exception("Dirty color")
    }
```

## while 和 for 迴圈
Kotlin 中有 while、do-while，這與 Java 沒有甚麼差別，所以不特別說明
在這裡先介紹區間與級數
- 區間:兩個值之間的間隔，例如 1..10 是一個區間(range)
- 級數:如果可以迭代一個區間的所有值，則此區間稱為級數(progression)

以下介紹 for 迴圈的常用方式
```kotlin=
for (i in 1..10)
    println(i) //1、2...10
for (i in 1 until 10)
    println(i) //1、2...9
for (i in 10 downTo 1 step 2)
    println(i) //10、8...2
```

==迭代映射==
將 map(映射) 的 key、value 對應兩個變數
```kotlin=
val map = mutableMapOf<Char, String>()
for (c in 'A'..'D') {
    val binary = Integer.toBinaryString(c.toInt())
    map[c] = binary
}
    
for ((letter, binary) in map)
    println("$letter = $binary")
```

==使用 in 檢查集合、區間成員==
in 可以用於檢查集合、區間的成員
```kotlin=
fun isLetter(c: Char) = when (c) {
    in 'a'..'z' -> "Yes, it is lower"
    in 'A'..'Z' -> "Yes, it is upper"
    else -> "No"
}

println("Kotlin" in setOf("Java", "Kotlin")) //true
```

## 例外
跟 Java 的例外處理相似，使用後若有例外則可以捕獲並處理
若無使用且發生例外，則此例外就會重新進入堆疊
```kotlin=
val percentage = 
    if (num in 0..100)
        num
    else
        throw IllegalArgumentException("num must between 0 and 100")
```

try、catch、finally 跟 Java 不同之處在於 throws 子句不存在了
```kotlin=
fun readNum(reader: BufferedReader): Int? {
    try {
        val line = reader.readLine()
        return Integer.parseInt(line)
    } catch (e: NumberFormatException) {
        return null
    } finally {
        reader.close()
    }
}
```

try 也可以作為表達式
```kotlin=
fun readNum(reader: BufferedReader) {
    val num = try {
        Integer.parseInt(reader.readLine())
    } catch (e: NumberFormatException) {
        null
    }
    
    println(num)
}
```

## 參考文章
[列舉](http://www.liying-cn.net/kotlin/docs/reference/enum-classes.html)
[List、Map、Set 集合](http://tw-hkt.blogspot.com/2018/11/kotlin-listof.html)

> 推薦書籍：Kotlin 實戰手冊 Dmitry Jemerov Svetlana Isakova 著/蔡明志 譯

## 未完事項
- typealias
- 中綴表示法、中綴函數 infix