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