1. 程式人生 > >Kotlin官方參考整理——05.其他

Kotlin官方參考整理——05.其他

5.1 解構宣告

見《03類和物件2.md》->“解構宣告”

5.2 集合

與大多數語言不同,Kotlin區分只讀集合和可讀寫集合,這有助於消除bug和設計良好的API。

只讀 可讀寫
List<out T> MutableList<T>
Set<out T> MutableSet<T>
Map<K, out V> MutableMap<K, V>

以List集合為例:

//只讀集合
val list = listOf(1, 2, 3)                  //list的型別是List<out T>,即只支援讀,不支援寫。泛型被out修飾,因此List<out T>是一個可協變的型別
//list.add(4) //編譯報錯,不允許寫操作 //可讀寫集合 val mutableList = mutableListOf(1, 2, 3) //mutableList的型別是MutableList<T>,支援讀寫。MutableList<T>不可協變也不可逆變 mutableList.add(4)

可讀寫集合是對應的只讀集合的子類,因此可以將一個可讀寫集合的物件賦值給一個只讀集合的引用:

val numbers: MutableList<Int> = mutableListOf(1, 2
, 3) val readOnlyView: List<Int> = numbers//MutableList是List的子類,因此可以這樣做 println(numbers) // 輸出 "[1, 2, 3]" numbers.add(4) println(readOnlyView) // 輸出 "[1, 2, 3, 4]" //readOnlyView.add(5) // 編譯報錯,不允許寫操作

可以為可讀寫集合建立一個只讀的副本:

val mutableList = mutableListOf<String>()
val list = mutableList.toList()

集合的一些習慣用法

val items = listOf(1, 2, 3, 4)
print(items.first())
print(items.last())
//filter接受一個函式為引數,函式型別為“(T) -> Boolean”
//filter會將集合中的每一個元素分別傳入該函式,如果函式返回true,則保留該元素
items.filter { it % 2 == 0 } // 返回 [2, 4]

val rwList = mutableListOf(1, 2, 3)
//返回集合中非null的元素,這裡會返回[1, 2, 3]
rwList.requireNoNulls()
//rwList.none { it > 6 }:集合中是否沒有大於6的元素
if (rwList.none { it > 6 }) println("No items above 6") //輸出“No items above 6”
//返回集合中的第一個元素,如果集合為空則返回null
val item = rwList.firstOrNull()

val readWriteMap = hashMapOf("foo" to 1, "bar" to 2)
println(readWriteMap["foo"]) //輸出“1”

5.3 區間

見《01開始.md》->“使用區間”

5.4 型別的檢查與轉換

見《01開始.md》->“型別檢測與轉換”

5.5 this表示式

  • 類的成員中,this指的是該類的當前物件
  • 擴充套件函式或者帶接收者的函式字面值中,this表示呼叫時位於“.”左側的接收者物件

帶標籤的this表示式參考《03類和物件2.md》->“巢狀類、內部類、匿名內部類”

5.6 相等性

Kotlin中有兩種型別的相等性:

  • 引用相等(兩個引用指向同一物件則引用相等)
  • 結構相等(也可稱為值相等。呼叫equals(),如果返回true則結構相等)

引用相等由===(否定形式為!==)來判斷。a===b當且僅當a和b指向同一個物件時為true。

結構相等由==(否定形式為!=)來判斷。如果a不是null則呼叫equals函式,如果a是null則檢視b是否為null。

當然了,==和===兩端的運算元必須是同一型別,否則無法通過編譯,提示型別不相容。

5.7 操作符過載

類似於C++,Kotlin也支援操作符過載。過載操作符可以通過類的成員函式或擴充套件函式來進行。過載操作符的函式前面需要加上“operator”修飾符。

操作符過載一般用得不多,詳見官方參考。

5.8 空安全

見《01開始.md》->“空安全”

5.9 異常

Kotlin中異常的使用方式與Java中基本一樣,要注意的主要是以下幾點:

Kotlin中的所有異常都是非受檢異常,沒有受檢異常

Java中的異常分為受檢異常(Checked Exception)和非受檢異常(Unchecked Exception)。

  • 受檢異常:必須被顯式地捕獲或者傳遞的異常。Java中大部分的異常都是受檢異常。
  • 非受檢異常:無須被顯式地捕獲或者傳遞的異常。Java中的非受檢異常只有一種,即RuntimeException及其子類。

Kotlin中的try…語句可以當做表示式使用

val a: Int? = try {
    parseInt(input)
} catch (e: NumberFormatException) {
    null
}

try…表示式的值是try塊中的最後一個表示式的值或者是(所有)catch塊中的最後一個表示式的值。finally塊中的內容不會影響try…表示式的值。

在Kotlin中throw也可以當做表示式使用

val s = person.name ?: throw IllegalArgumentException("Name required")

throw表示式的型別是特殊型別Nothing。該型別沒有值,而是用於標記永遠不能達到的程式碼位置(即程式執行時永遠到達不了需要用到該值地方)。你也可以使用Nothing來標記一個永遠不會返回的函式:

fun fail(message: String): Nothing {
    throw IllegalArgumentException(message)
}

//注意:此處s的型別是非空型別,因為fail函式的型別是Nothing
//如果person.name為空的話,那麼會執行fail函式,而fail函式是永遠不可能返回的,也就是說只要執行了fail函式,那麼永遠執行不到將等號右邊表示式的值賦給s這一步
//因此,只要執行了將等號右邊表示式的值賦給s這一步,說明person.name必然是非空的,那麼s也就必然是非空的
val s = person.name ?: fail("Name required")
println(s)

5.10 註解

定義註解的方式與Java中不同,詳見官方參考。

5.11 反射

官方參考講的很簡略,瞭解即可

反射允許在執行時自省你的程式的結構。所謂自省即在執行時獲悉一個名稱、或屬性、或函式的型別。

在使用反射之前,請確保已將kotlin-reflect.jar新增到了專案的classpath中。

5.11.1 類引用

類引用是KClass的例項。

  • 通過 類名或物件名::class 可以獲得Kotlin的類型別(即KClass)的例項
  • 通過 類名或物件名::class.java 可以獲得Java的類型別(即Class)的例項
class SampleClass

fun test() {
    //獲得KClass的例項
    val kotlinClass: KClass<SampleClass> = SampleClass::class
    kotlinClass.constructors//獲取構造方法
    KClass沒有提供獲取方法和屬性的途徑
    ...

    //獲得Class的例項
    val javaClass: Class<SampleClass> = SampleClass::class.java
    javaClass.getConstructor()//獲取構造方法
    javaClass.getField()//獲取屬性
    javaClass.getMethod()//獲取成員方法
    ...

    val sampleClass = SampleClass()
    sampleClass::class//獲得KClass的例項
    sampleClass::class.java//獲得Class的例項
}

5.11.2 函式引用

函式引用是KFunction的例項。

當我們有一個命名函式宣告如下:

fun isOdd(x: Int) = x % 2 != 0

我們可以很容易地直接呼叫它,如isOdd(5);但是我們也可以通過“::”操作符來獲取函式的引用,並將函式引用傳遞給另一個函式:

val numbers = listOf(1, 2, 3)
//filter接受一個“(Int)->Boolean”型別的函式為引數
println(numbers.filter(::isOdd))

如果一個函式是類的成員函式或擴充套件函式,則“::”前必須加上類名或物件名作為限定:

class Haha {
    fun test() {}
}

fun test() {
    val method1 = Haha::test    //通過類來獲取方法引用
    method1(Haha()) //呼叫時需要傳入方法所屬的類的物件

    val method2 = Haha()::test  //通過物件來獲取方法引用
    method2()       //呼叫時無需傳入方法所屬的類的物件
}

5.11.3 屬性引用

屬性引用是KProperty(對於val)KMutableProperty(對於var)的例項。KMutableProperty是KProperty的子類。

通過“::”運算子來獲取屬性引用,獲得屬性引用之後,可以呼叫其getter和setter等方法。如果屬性是類的成員,則::前要加上類名或物件名作為限定:

var m = 3

class AA {
    var x = 1
    val y = 2

    fun test() {
        /**
         * 通過類名獲取成員變數的引用
         */
        //可讀寫成員變數
        AA::x.get(this)//需要傳入AA的物件,這裡使用了this
        AA::x.set(this, 2)
        //只讀成員變數
        AA::y.get(this)
        //AA::y.set()//y是隻讀的,無此方法

        /**
         * 通過物件名獲取成員變數的引用
         */
        val aa = AA()
        aa::x.get()
        aa::x.set(2)
        aa::y.get()
        //aa::y.set()

        /**
         * 獲取頂層變數的引用
         */
        ::m.get()
        ::m.set(1)
    }
}

5.11.4 建構函式引用

建構函式可以像方法和屬性那樣引用。格式:::類名

class Foo

//接受一個無參並返回Foo型別物件的函式為引數
fun function(factory: () -> Foo) {
    val x: Foo = factory()
}

fun test(){
    function(::Foo)//將Foo的建構函式傳遞給function函式
}

5.12 型別安全的構建器

瞭解,詳見官方參考

構建器可以讓我們以非常簡單明晰的格式來建立一段複雜的html程式碼字串、xml程式碼字串等。

fun result(args: Array<String>) =
    html {
        head {
            title { +"XML encoding with Kotlin" }
        }

        body {
            h1 { +"XML encoding with Kotlin" }

            p { +"this format can be used as an alternative markup to XML" }

            a(href = "http://kotlinlang.org") { +"Kotlin" }

            p {
                +"This is some"
                b { +"mixed" }
                +"text. For more see the"
                a(href = "http://kotlinlang.org") { +"Kotlin" }
                +"project"
            }

            p { +"some text" }

            p {
                for (arg in args)
                    +arg
            }
        }
    }

這是完全合法的Kotlin程式碼。下面我們看看它的實現原理。

首先想想為什麼我們可以在程式碼中這樣寫:

html {
    ...
}

html實際上是一個函式呼叫,它接受一個lambda表示式作為引數。該函式定義如下:

//init需要通過一個HTML物件來呼叫,因此在init函式內部,可以直接呼叫HTML類中的函式,不需要任何字首
fun html(init: HTML.() -> Unit): HTML {
    val html = HTML()
    html.init()
    return html
}

這個函式接受一個名為init的函式為引數,該函式引數的型別是HTML.() -> Unit,它是一個帶接收者的函式型別,這意味著我們需要通過一個HTML物件(即接收者)來呼叫init函式,並且我們可以在init函式內部直接呼叫接收者的成員:

html {
    //head和body是HTML的成員函式
    head { ... }
    body { ... }
}

5.13 類型別名

Kotlin通過typealias關鍵字來為一個已有型別起一個別名。

//給集合型別起別名
typealias NodeSet = Set<Network.Node>
typealias FileTable<K> = MutableMap<K, MutableList<File>>

//給函式型別起別名
typealias MyHandler = (Int, String, Any) -> Unit
typealias Predicate<T> = (T) -> Boolean

//給巢狀類、內部類起別名
class A {
    class Nested
    inner class Inner
}
typealias ANested = A.Nested
typealias AInner = A.Inner