Taiwan Kotlin User Group

Logo

Taiwan Kotlin User Group 的網站,在台灣推廣 Kotlin 程式語言,舉辦相關活動。如果對 Kotlin 有興趣,想要多瞭解一些,歡迎來我們的社群一起聚會!

View My GitHub Profile

Kotlin 慣用寫法

下面列出一些常見的 Kotlin 慣用寫法

建立 DTO

建立 DTO

(或稱 POJO,Plain Old Java Object)

data class Customer(val name: String, val email: String)

建立 Customer 類別

並包含下列函數:

函數參數的預設值

fun foo(a: Int = 0, b: String = "") { ... }

過濾 list

val positives = list.filter { x -> x > 0 }

更短的寫法

val positives = list.filter { it > 0 }

確認某元素是否出現在集合內

if ("john@example.com" in emailsList) { ... }

if ("jane@example.com" !in emailsList) { ... }

字串樣板

println("Name $name")

檢查變數型態

when (x) {
    is Foo -> ...
    is Bar -> ...
    else   -> ...
}

建立唯讀 list

val list = listOf("a", "b", "c")

建立唯讀 map

val map = mapOf("a" to 1, "b" to 2, "c" to 3)

存取 map

println(map["key"])
map["key"] = value

遍歷 map

for ((k, v) in map) {
    println("$k -> $v")
}

也可用來遍歷元素為 Pair 型態的 list

val list = listOf(  
    Pair("name", "Alice"),   
    Pair("age", 18)  
)  
for ((k, v) in list) {  
    println("$k -> $v")  
}

kv 都可以換成可讀性更高的名稱

像是 nameage

某範圍內迴圈

從 1 到 100

for (i in 1..100) { ... }

從 1 到 99,不包含 100

for (i in 1 until 100) { ... }

2 4 6 8 10

for (x in 2..10 step 2) { ... }

從 10 到 1

for (x in 10 downTo 1) { ... }

某範圍內判斷

if (x in 1..10) { ... }

Lazy property

val p: String by lazy {
    // compute the string
}

擴充函數

可在原生型態上增添新函數

fun String.spaceToCamelCase() { ... }

"Convert this to camelcase".spaceToCamelCase()

建立單例

建立單例(singleton)

object Resource {
    val name = "Name"
}

實例化抽象類別

abstract class MyAbstractClass {
    abstract fun doSomething()
    abstract fun sleep()
}

fun main() {
    val myObject = object : MyAbstractClass() {
        override fun doSomething() {
            // ...
        }

        override fun sleep() { // ...
        }
    }
    myObject.doSomething()
}

If-not-null 縮寫

如果 files 不為 null

印出 files.size

println(files?.size)

If-not-null-else 縮寫

如果 files 不為 null

印出 files.size

如果 filesnull

印出 "empty"

println(files?.size ?: "empty")

null 執行特定行為

如果 values["email"]null

拋出例外 IllegalStateException("Email is missing!")

val email = values["email"] 
    ?: throw IllegalStateException("Email is missing!")

在可能為空的集合取出第一個元素

emailsList<String>

如果集合為空,則取出預設物件(""

val mainEmail = emails.firstOrNull() ?: ""

變數不為 null 時執行

value?.let {
    // 變數不為 null 時執行此段落
}

Map nullable value if not null

假設 value 型態是 Int?

mapped 轉換成 List<Int>

如果 valuenull

mapped 設置為 listOf(0)

val mapped = value?.let { listOf(it) } ?: listOf(0)

回傳 when 表達式

fun transform(color: String): Int {
    return when (color) {
        "Red" -> 0
        "Green" -> 1
        "Blue" -> 2
        else -> 
            throw IllegalArgumentException("Invalid color")
    }
}

try-catch 表達式

在 Kotlin try-catch 是表達式

回傳結果可以直接寫入變數

fun test() {
    val result = try {
        count()
    } catch (e: ArithmeticException) {
        throw IllegalStateException(e)
    }

    // Working with result
}

if 表達式

在 Kotlin if 是表達式

回傳結果可以直接寫入變數

fun foo(param: Int) {
    val result = if (param == 1) {
        "one"
    } else if (param == 2) {
        "two"
    } else {
        "three"
    }
}

利用回傳 Unit 函數的生成器模式

fill() 的回傳值是 Unit

(對等其他語言的回傳 void

下面建立大小為 size

全部值為 -1IntArray

fun arrayOfMinusOnes(size: Int): IntArray {
    return IntArray(size).apply { fill(-1) }
}

單一表達式的函數

fun theAnswer() = 42

上面等同於

fun theAnswer(): Int {
    return 42
}

單一表達式函數搭配上其他慣用寫法

可以寫出更簡潔的程式

例如搭配上 when 表達式

fun transform(color: String): Int = when (color) {
    "Red" -> 0
    "Green" -> 1
    "Blue" -> 2
    else -> 
        throw IllegalArgumentException("Invalid color")
}

對某物件內多個函數進行呼叫

利用 with()

class Turtle {
    fun penDown()
    fun penUp()
    fun turn(degrees: Double)
    fun forward(pixels: Double)
}

val myTurtle = Turtle()

// 畫出大小 100 畫素的正方形
with(myTurtle) { 
    penDown()
    for (i in 1..4) {
        forward(100.0)
        turn(90.0)
    }
    penUp()
}

設定物件屬性

利用 apply()

val myRectangle = Rectangle().apply {
    length = 4
    breadth = 5
    color = 0xFAFAFA
}

在處理沒有出現在建構子內的屬性時

非常方便的寫法

Java 7 的 try-with-resources

val stream = Files.newInputStream(
        Paths.get("/some/file.txt")
    )
stream.buffered().reader().use { reader ->
    println(reader.readText())
}

需要泛型類別資訊的泛型函數

假設 GsonGson.fromJson()

的宣告為

public final class Gson {
    // ...
    public <T> T fromJson(JsonElement json, Class<T> classOfT) 
        throws JsonSyntaxException {
    // ...

使用時我們會需要 Class<T> 的資訊

利用 inline funreified 關鍵字

我們可以讓泛型函數內

取得泛型參數的類別資訊

inline fun <reified T: Any> Gson.fromJson(json: JsonElement): T =
    this.fromJson(json, T::class.java)

這邊 <T> 型態是 Any

可以放入任意類別

使用起來非常簡潔

Gson.fromJson<MyDataClass>(json)

可為 null 的 Boolean

val b: Boolean? = ...
if (b == true) {
    ...
} else {
    // `b` 為 false 或 null
}

交換變數

a = b.also { b = a }

標記程式未完成

利用 TODO() 函數

TODO() 執行時會拋出 NotImplementedError 中斷程式

TODO() 的回傳值是 Nothing

所以可以無視函數設定的回傳值

TODO()內可以宣告理由

fun calcTaxes(): BigDecimal = TODO("等會計部門回需求")

IntelliJ IDEA 的 kotlin plugin 認得 TODO()

並會自動將內容放在 TODO 工具視窗內


想了解更多嗎?

可以看看 Kotlin 語法特色

或加入 kotlin.tips 的 Kotlin 讀書會