前言
人生苦多界牡,快來 Kotlin 申眼,快速學(xué)習(xí)Kotlin股囊!
什么是Kotlin?
Kotlin 是種靜態(tài)類型編程語言 用于現(xiàn)代多平臺(tái)應(yīng)用 100%可與Java?和Android?互操作,它是[JetBrains]開發(fā)的基于JVM的語言
開發(fā)IDE : Intellij / AndroidStudio3.0 preview
參考: Kotlin 官網(wǎng) / Kotlin 語言中文站
Example
Github KotlinDemo
Hexo Site
源文件與包
包
Kotlin 源文件以 kt 結(jié)尾. 源文件所有內(nèi)容(無論是類還是函數(shù))都包含在聲明的包內(nèi).
NOTE: 源文件通常以包聲明開頭, 沒有指明包谍婉,則該文件的內(nèi)容屬于無名字的默認(rèn)包(屬于root package)舒憾。
package demo
NOTE: 若聲明的包路徑與文件路徑不一致,亦可以正常編譯. 不過會(huì)有如Java 一樣的警告 Package directive doesn't match file location
默認(rèn)導(dǎo)入
Kotlin 如同Java 一樣 默認(rèn)源文件會(huì)導(dǎo)入以下包:
kotlin.*
kotlin.annotation.*
kotlin.collections.*
kotlin.comparisons.* (自 1.1 起)
kotlin.io.*
kotlin.ranges.*
kotlin.sequences.*
kotlin.text.*
//根據(jù)目標(biāo)平臺(tái)還會(huì)導(dǎo)入額外的包:
JVM:
java.lang.*
kotlin.jvm.*
JS:
kotlin.js.*
import
import (used by preamble)
: "import" SimpleName{"."} ("." "*" | "as" SimpleName)? SEMI?
;`
Java vs Kotlin
1.如果出現(xiàn)名字沖突穗熬,Kotlin 可以使用 as 關(guān)鍵字在本地重命名沖突項(xiàng)來消歧義
2.Kotlin 的關(guān)鍵字 import 不僅僅限于導(dǎo)入類镀迂,還可以導(dǎo)入頂層函數(shù)及屬性,在對(duì)象聲明中聲明的函數(shù)和屬性唤蔗,枚舉常量等.
NOTE: 與 Java 不同探遵,Kotlin 沒有單獨(dú)的 import static
語法窟赏; 所有這些聲明都用 import 關(guān)鍵字導(dǎo)入。
頂層聲明
1.同 package 下的Kotlin 源文件,在頂層所聲明的常量,變量以及函數(shù)等不允許重復(fù)定義,否則報(bào)Conflicting 錯(cuò)誤箱季。
2.若聲明用 private 可見性修飾符修飾時(shí),屬于當(dāng)前文件私有涯穷。
基本數(shù)據(jù)類型
Numbers
Kotlin 一切都是對(duì)象。Kotlin提供以下代表數(shù)字藏雏、字符的內(nèi)置類型(這很接近Java)
Type | Bit width | 包裝器類型 |
---|---|---|
Double | 64 | Double |
Float | 32 | Float |
Long | 64 | Long |
Int | 32 | Integer |
Short | 16 | Short |
Byte | 8 | Byte |
Char | 16 (Unicode character) | Character |
NOTE:僅 Char 不是Kotlin的數(shù)字拷况。如下
val c:Char='c'
val i: Int = c.toInt()
println(c) // 'c'
println(i) // 99
字面常量
Kotlin 唯獨(dú)不支持八進(jìn)制
- 十進(jìn)制:
123
- 二進(jìn)制:
0b00001011
- 十六進(jìn)制:
0x0F
Kotlin 數(shù)值表示方法
- 默認(rèn) Double:
123.5
、123.5e10
- Float 用
f
或者F
標(biāo)記:123.5f
- Long 用大寫
L
標(biāo)記:123L
NOTE:支持?jǐn)?shù)字字面值中的下劃線(自 kotlin1.1 起)
val oneMillion = 1_000_000
Kotlin 裝箱機(jī)制
Kotlin 內(nèi)置類型在 Java 平臺(tái)上是存儲(chǔ)為 JVM 的原生類型掘殴,但一個(gè)可空的引用(如 Int?
)或泛型情況下(如 Array<Int>,List<Int> ...
) 會(huì)把數(shù)字和字符自動(dòng)裝箱成相應(yīng)包裝類, 請參考 Numbers赚瘦。
val low = -127
val high = 127
val noInIntegerCache = 128
var boxedA: Int? = low
var anotherBoxedA: Int? = low
println(boxedA == anotherBoxedA) //true
println(boxedA === anotherBoxedA) //true
boxedA = high
anotherBoxedA = high
println(boxedA == anotherBoxedA) //true
println(boxedA === anotherBoxedA) //true
boxedA = noInIntegerCache
anotherBoxedA = noInIntegerCache
println(boxedA == anotherBoxedA) //true
println(boxedA === anotherBoxedA) //false
==
和 ===
請參考 類型相等性 。
val anIntegerA: Int? = 123 //對(duì)應(yīng) java.lang.Integer 一個(gè)裝箱的 Int
val anIntegerB: Int? = 123 //對(duì)應(yīng) java.lang.Integer
println(anIntegerA === anIntegerB) //true
println(anIntegerA?.javaClass) //int
println((anIntegerA as Number).javaClass) //java.lang.Integer
val anIntegerArray: Array<Int> = arrayOf(1,2,3)
val anIntegerList: List<Int> = listOf(1,2,3)
println(anIntegerArray.toString())
println(anIntegerList.toString())
println((anIntegerList[0] as Number).javaClass) //
NOTE:一個(gè)可空的引用(如 Int?
)能不能裝換成 Int
,答案是肯定的杯巨。強(qiáng)制轉(zhuǎn)換或者 !!
val anIntegerA: Int? = 123
val anNewIntA: Int = anIntegerA //編譯錯(cuò)誤
val anNewIntB: Int = anIntegerA!! //或者 anIntegerA as Int
val anNewIntC: Int = anIntegerA //Start cast to kotlin.Int
顯式轉(zhuǎn)換
每個(gè)數(shù)字類型支持如下的轉(zhuǎn)換:
toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char
如下示例 Java 中,包裝類不能隱式轉(zhuǎn)換, Kotlin 也是如此, 不同類型之間是不能相互隱式轉(zhuǎn)換的蚤告。
Byte a = 1;
Integer b = a;//error
val b: Byte = 1 // OK, 字面值是靜態(tài)檢測的
val i: Int = b // 錯(cuò)誤
運(yùn)算
Kotlin支持?jǐn)?shù)字運(yùn)算的標(biāo)準(zhǔn)集,運(yùn)算被定義為相應(yīng)的類成員(但編譯器會(huì)將函數(shù)調(diào)用優(yōu)化為相應(yīng)的指令)服爷。 參見操作符重載杜恰。
對(duì)于位運(yùn)算,沒有特殊字符來表示仍源,而只可用中綴方式調(diào)用命名函數(shù)心褐,例如:
val x = (1 shl 2) and 0x000FF000
這是完整的位運(yùn)算列表(只用于 Int
和 Long
):
- shl(bits) – 有符號(hào)左移 (Java 的 <<)
- shr(bits) – 有符號(hào)右移 (Java 的 >>)
- ushr(bits) – 無符號(hào)右移 (Java 的 >>>)
- and(bits) – 位與
- or(bits) – 位或
- xor(bits) – 位異或
- inv() – 位非
字符串
字符串用 String
類型表示。字符串是不可變的笼踩。 字符串的元素——字符可以使用索引運(yùn)算符訪問: s[i]
逗爹。 可以用 for 循環(huán)迭代字符串:
val s = "Hello, world!\n" //字符串字面值
//字符串
for (c in s) {
print(c)
}
//原生字符串 使用三個(gè)引號(hào)(""")分界符括起來
val text = """
for (c in s) {
print(c)
}
"""
println(text)
//字符串模板
val str = "$s.length is ${s.length}"
println(str)
NOTE: 模板表達(dá)式以美元符($
)開頭,若要對(duì)象屬性時(shí)要花括號(hào)括起來,若要表示字面值 $
字符z則:
val price = "${'$'}9.99"
println(price)
數(shù)組
數(shù)組在 Kotlin 中使用 Array
類來表示嚎于,它定義了 get
和 set
函數(shù)(按照運(yùn)算符重載約定這會(huì)轉(zhuǎn)變?yōu)?[]
)和 size
屬性掘而,以及一些其他有用的成員函數(shù):
class Array<T> private constructor() {
val size: Int
operator fun get(index: Int): T
operator fun set(index: Int, value: T): Unit
operator fun iterator(): Iterator<T>
// ……
}
Library.kt
中 arrayOf()
arrayOfNulls
函數(shù)以及Array
構(gòu)造函數(shù)能創(chuàng)建數(shù)數(shù)組:
val args: Array<Int> = arrayOf(1, 2, 3)
val arrayOfNulls = arrayOfNulls<Int>(10) //空數(shù)組
val initArray = Array(5, { i -> (i * i).toString() }) //構(gòu)造函數(shù)init
println(arrayOfNulls.size)
NOTE: 與 Java 不同的是,Kotlin 中數(shù)組是不型變的(invariant)于购。這意味著 Kotlin 不讓我們把 Array<String>
賦值給 Array<Any>
袍睡,以防止可能的運(yùn)行時(shí)失斒鹩取(但是你可以使用 Array<out Any>
, 參見類型投影)揭芍。
之前所說的在泛型情況下Kotlin 會(huì)把數(shù)字和字符自動(dòng)裝箱成相應(yīng)包裝類, Arrays.kt
中有以下
ByteArray
CharArray
ShortArray
IntArray
LongArray
FloatArray
DoubleArray
BooleanArray
無裝箱開銷的專門的類來表示原生類型數(shù)組, 和 Array
并沒有繼承關(guān)系,但是它們有同樣的方法屬性集饭聚。它們也都有相應(yīng)的工廠方法嫌吠。
val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]
數(shù)組迭代通過 iterator()
函數(shù)返回 Iterator<T>
對(duì)象進(jìn)行迭代:
val iterator = args.iterator()
while (iterator.hasNext()) {
print("" + iterator.next())
}
println()
//forEach
args.iterator().forEach { print(it) }
println()
//for-
for (it in initArray/*.iterator()*/) {
print(it)
}
println()
//下標(biāo)索引
args.forEachIndexed { index, i -> println("$index = $i") }
NOTE: forEach
forEachIndexed
這些是Array 的擴(kuò)展函數(shù), 背后實(shí)現(xiàn)也是 [for 循環(huán) ](#For 循環(huán))
區(qū)間
區(qū)間表達(dá)式由具有操作符形式 ..
的 rangeTo
和 downTo
函數(shù)輔以 in
和 !in
形成止潘。 區(qū)間是為任何可比較類型(Comparable<in T>
)定義的,但對(duì)于整型原生類型(Int ,Long,Char)辫诅,Ranges.kt
中實(shí)現(xiàn)了常用的整型區(qū)間(IntRange
凭戴、 LongRange
、 CharRange
),而在 Primitives.kt
中的 Int ,Long,Char
類實(shí)現(xiàn)了rangeTo
函數(shù)泥栖。以下是使用區(qū)間的一些示例
println((1.rangeTo(3)).contains(1)) //使用區(qū)間rangeTo函數(shù)
println(1 in (1..3)) //使用區(qū)間操作符
..
創(chuàng)建一個(gè)區(qū)間, 實(shí)際是調(diào)用 rangeTo
函數(shù)返回原生類型 *Range
對(duì)象, in
則調(diào)用 contains
函數(shù)簇宽。in *Range
還可以用在迭代(for-循環(huán))中勋篓。
for (index in 1..4) print(index)
NOTE:rangeTo
創(chuàng)建的區(qū)間, 范圍值是小到大, downTo
反之吧享。他們默認(rèn) step 分別為1魏割,-1
// val intRange = 1..4 //step 1 default
val intRange = 1..4 step 2 //step 2
val is2 = 2 in intRange
val is4 = 4 in intRange
println("first = ${intRange.first},last = ${intRange.last},step = ${intRange.step}")
println(is2)
println(is4)
println(is2 or is4)
// for (index in 1..4) print(index)
for (index in intRange) print(index)
println()
for (index in intRange.reversed()) print(index)
println()
for (index in 10..1) print(index) //Nothing
println()
val intProgression = 10 downTo 1 /*step 2*/ //step默認(rèn)為1 倒序迭代
println("first = ${intProgression.first},last = ${intProgression.last},step = ${intProgression.step}")
for (index in intProgression) print(index)
println()
for (index in 1..4) print(index) // 輸出“1234”
val isIn = 3 in 1.rangeTo(100)
println(isIn)
for (i in 'a'..'z') print(i)
背后實(shí)現(xiàn)原理
區(qū)間實(shí)現(xiàn)了該庫中的一個(gè)公共接口:ClosedRange<T>
。
ClosedRange<T>
在數(shù)學(xué)意義上表示一個(gè)閉區(qū)間钢颂,它是為可比較類型定義的钞它。 它有兩個(gè)端點(diǎn):start
和 endInclusive
他們都包含在區(qū)間內(nèi)。 其主要操作是 contains
殊鞭,通常以 in/!in 操作符形式使用遭垛。
整型數(shù)列(IntProgression
、 LongProgression
操灿、 CharProgression
)表示等差數(shù)列锯仪。 數(shù)列由 first
元素、last
元素和非零的 step
定義趾盐。 第一個(gè)元素是 first
庶喜,后續(xù)元素是前一個(gè)元素加上 step
。 last
元素總會(huì)被迭代命中救鲤,除非該數(shù)列是空的久窟。
數(shù)列是 Iterable<N>
的子類型,其中 N
分別為 Int
本缠、 Long
或者 Char
斥扛,所以它可用于 for-循環(huán)以及像 map
、filter
等函數(shù)中丹锹。 對(duì) Progression
迭代相當(dāng)于 Java/JavaScript 的基于索引的 for-循環(huán):
for (int i = first; i != last; i += step) {
// ……
}
對(duì)于整型類型稀颁,..
操作符創(chuàng)建一個(gè)同時(shí)實(shí)現(xiàn) ClosedRange<T>
和 *Progression
的對(duì)象。 例如楣黍,IntRange
實(shí)現(xiàn)了 ClosedRange<Int>
并擴(kuò)展自 IntProgression
匾灶,因此為 IntProgression
定義的所有操作也可用于 IntRange
。 downTo()
和 step()
函數(shù)的結(jié)果總是一個(gè) *Progression
锡凝。
數(shù)列由在其伴生對(duì)象中定義的 fromClosedRange
函數(shù)構(gòu)造:
IntProgression.fromClosedRange(start, end, step)
數(shù)列的 last
元素這樣計(jì)算:對(duì)于正的 step
找到不大于 end
值的最大值粘昨、或者對(duì)于負(fù)的 step
找到不小于 end
值的最小值,使得 (last - first) % increment == 0
窜锯。
一些實(shí)用函數(shù)
- rangeTo()
- downTo()
- reversed()
- step()
程序結(jié)構(gòu)
變量
分別使用 var ,val 聲明可變和不可變的變量.例子如下
val s = "Example" // A Immutable String
var v = "Example" // A Mutable String
聲明可變變量語法
var <propertyName>[: <PropertyType>] [= <property_initializer>]
聲明不可變變量(僅賦值一次只讀變量)語法
var <propertyName>[: <PropertyType>] = <property_initializer>
默認(rèn) Kotlin 變量類型是能通過賦值時(shí)智能推斷該變量的類型,且該var
變量只能該類型的的值张肾。顯式確定變量類型,必須要接收該類型的初始化。通過一個(gè)簡單例子說明
val aImmutableIntVariable = 0x001 //aImmutableIntVariable 類型為 Int
var aMutableIntVariable: Int = "0x002" //語法error
var bMutableIntVariable: Int = 0x002
var cMutableVariable: Any //顯式確定變量類型,必須要接收該類型的初始化锚扎。
aImmutableIntVariable = 1 //不能重新分配 Val cannot be reassigned
bMutableIntVariable = ""http://一旦類型確定,只能接受該類型的值
NOTE: var
變量直接賦值為 null
,該變量則不符合預(yù)期的類型 簡單來說(Nothing),再次賦值時(shí)報(bào)錯(cuò)吞瞪。
var aNullable = null
aNullable = 1;//Nothing
更詳細(xì)的類型介紹:類型安全和智能轉(zhuǎn)換
常量 (編譯期)
已知值的屬性可以使用 const 修飾符標(biāo)記為 編譯期常量.必須滿足以下需求
- 位于頂層或者是 object 的一個(gè)成員
- String 或原生類型 值初始化
- 沒有自定義 getter
const val CONST_VAL = 1
//const val CONST_VAL_GET get() = 1 //Error: Const 'val' should not have a getter
//const val CONST_VAL_TEST :Any = 1 //error
fun testConstInFunction() {
// const val CONST_VAL = 1 //error
}
object Kotlin {
const val CONST_VAL: String = "object 常量"
}
幕后字段
Kotlin 中類不能有字段。然而驾孔,當(dāng)使用自定義訪問器時(shí)芍秆,有時(shí)有一個(gè)幕后字段(backing field)有時(shí)是必要的惯疙。為此 Kotlin 提供一個(gè)自動(dòng)幕后字段,它可通過使用 field
標(biāo)識(shí)符訪問妖啥。
var counter = 0 // 此初始器值直接寫入到幕后字段
set(value) {
if (value >= 0)
field = value
}
field
標(biāo)識(shí)符只能用在屬性的訪問器內(nèi)霉颠。
如果屬性至少一個(gè)訪問器使用默認(rèn)實(shí)現(xiàn),或者自定義訪問器通過 field
引用幕后字段荆虱,將會(huì)為該屬性生成一個(gè)幕后字段蒿偎。
例如,下面的情況下怀读, 就沒有幕后字段:
val isEmpty: Boolean
get() = this.size == 0
如果你的需求不符合這套“隱式的幕后字段”方案诉位,那么總可以使用 幕后屬性(backing property):
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null) {
_table = HashMap() // 類型參數(shù)已推斷出
}
return _table ?: throw AssertionError("Set to null by another thread")
}
從各方面看,這正是與 Java 相同的方式菜枷。因?yàn)橥ㄟ^默認(rèn) getter 和 setter 訪問私有屬性會(huì)被優(yōu)化苍糠,所以不會(huì)引入函數(shù)調(diào)用開銷。
控制流:if啤誊、when岳瞭、for、while
if 語句坷衍、if - else 表達(dá)式
在 Kotlin 中,沒有Java中三元運(yùn)算符(條件 ? 然后 : 否則), 因?yàn)?strong>if - else 是一個(gè)表達(dá)式寝优,即它會(huì)返回一個(gè)值。
val num1 = 1
val num2 = 2
val max = if (num1 > num2) num1 else num2
println(max)
println(if (num1 < num2) "if - else 表達(dá)式" else num2)
if的分支可以是代碼塊枫耳,最后的表達(dá)式作為該塊的值:
println(
if (num1 < num2) {
println("num1 < num2")
"if - else 表達(dá)式"
} else num2
)
When 表達(dá)式
在 Kotlin 中,when
取代了Java中 switch
乏矾。聲明語法如下:
when[(表達(dá)式)]{
[條件分支1,條件分支2(可多個(gè) 逗號(hào)分隔)] -> controlStructureBody [SEMI]
[else 分支] -> controlStructureBody [SEMI]
}
SEMI 代表 ;或者 換行
, 在controlStructureBody 是代碼塊且有變量聲明時(shí)使用, 示例:
when {
} //最簡單的形式
val randomNum = Random().nextInt(5)
when (randomNum) {
0, 1 -> println("randomNum == 0 or randomNum == 1") //多個(gè)分支條件放在一起迁杨,用逗號(hào)分隔
2 -> println("randomNum == 2")
else -> println("otherwise")
}
//如果不提供參數(shù)钻心,所有的分支條件都是簡單的布爾表達(dá)式,而當(dāng)一個(gè)分支的條件為真時(shí)則執(zhí)行該分支
when {
randomNum == 0 -> {
var a = 2; println("is 0") //;
var b = 3
println("is 0") //換行
}
}
//其他分支都不滿足條件將會(huì)求值 else 分支
when {
randomNum == 0 -> println("randomNum is 0")
randomNum == 1 -> println("randomNum is 1")
else -> println("otherwise")
}
NOTE: when
作為一個(gè)表達(dá)式使用铅协,則必須有 else
分支捷沸, 除非所有的可能情況都已經(jīng)覆蓋了。
val an = when (1) {
1 -> 1
else -> "never arrive"
}
println(an)
when (randomNum == 3) {
true -> println("is 3")
false -> println(randomNum)
}
For 循環(huán)
for
循環(huán)可以對(duì)任何提供迭代器(iterator)的對(duì)象進(jìn)行遍歷狐史。語法
for (item in collection) print(item)
for (index in collection.indices) print(collection[index])
示例
val array = arrayOf(1, 2, 3)
//for
for (index in array.indices) print(array[index]);println() //索引遍歷一個(gè)數(shù)組或者一個(gè) list
for (item in array) print(item);println()
//庫函數(shù) forEachIndexed
array.forEachIndexed { index, item -> print("[$index] = $item \t")}
println()
//庫函數(shù) withIndex
for ((index, value) in array.withIndex()) {
println("the element at $index is $value")
}
如上所述痒给,for 可以循環(huán)遍歷任何提供了迭代器的對(duì)象。即:
- 有一個(gè)成員函數(shù)或者擴(kuò)展函數(shù)
iterator()
骏全,它的返回類型Iterator<T>
- 有一個(gè)成員函數(shù)或者擴(kuò)展函數(shù)
next()
- 有一個(gè)成員函數(shù)或者擴(kuò)展函數(shù)
hasNext()
返回Boolean
苍柏。
這三個(gè)函數(shù)都需要標(biāo)記為 operator
。
While 循環(huán)
while
和 do..while
照常使用姜贡。小白應(yīng)該也可以搞掂吧玲昧。令漂。身弊。
循環(huán)中的Break和continue
在循環(huán)中 Kotlin 支持傳統(tǒng)的 break
和 continue
操作符。
返回和跳轉(zhuǎn)
Kotlin 有三種結(jié)構(gòu)化跳轉(zhuǎn)表達(dá)式:
- return烛恤。默認(rèn)從最直接包圍它的函數(shù)或者匿名函數(shù)返回。
- break余耽。終止最直接包圍它的循環(huán)缚柏。
- continue。繼續(xù)下一次最直接包圍它的循環(huán)宾添。
標(biāo)簽
在 Kotlin 中任何表達(dá)式都可以用標(biāo)簽(label)來標(biāo)記船惨。 標(biāo)簽的格式為標(biāo)識(shí)符后跟 @
符號(hào)柜裸,例如:abc@
缕陕、fooBar@
都是有效的標(biāo)簽(參見語法)。 要為一個(gè)表達(dá)式加標(biāo)簽疙挺,我們只要在其前加標(biāo)簽即可扛邑。
Break 和 Continue 的標(biāo)簽控制跳轉(zhuǎn), return 標(biāo)簽控制返回目標(biāo),示例:
out@ for (i in 1..3) {
for (j in 1..3) {
if (j == 2) break@out;print("[$i , $j] ")
}
}
println()
out@ for (i in 1..3) {
for (j in 1..3) {
if (j == 2) continue@out;print("[$i , $j] ")
}
}
println()
var nullInt: Int? = 1
nullInt = null
var anLong = nullInt?.toLong() ?: return
fun performLabelReturn() {
val array = arrayOf(1, 2, 3)
array.forEach {
if (it == 2) return //
println(it)
}
println("performLabelReturn can't reach")
}
performLabelReturn()
println()
fun performLabelLambdaLimitReturn() {
val array = arrayOf(1, 2, 3)
array.forEach {
if (it == 2) return@forEach //
println(it)
}
println("performLabelLambdaLimitReturn can reach")
}
performLabelLambdaLimitReturn()
println()
fun performLabelLimitReturn() {
val array = arrayOf(1, 2, 3)
array.forEach limit@ {
if (it == 2) return@limit //
println(it)
}
println("performLabelLimitReturn can reach")
}
performLabelLimitReturn()
println()
fun performLabelAnonymousLambdaLimitReturn() {
val array = arrayOf(1, 2, 3)
array.forEach(fun(value: Int) {
if (value == 2) return // local return to the caller of the anonymous fun, i.e. the forEach loop
println(value)
})
println("performLabelAnonymousLambdaLimitReturn can reach")
}
performLabelAnonymousLambdaLimitReturn()
println()
fun a(): Int {
return@a 12
}
println(a())
面向?qū)ο?/h2>
類與對(duì)象
聲明類(class)語法
[訪問修飾符 默認(rèn)public] [非訪問修飾符 默認(rèn) final] class 類名
[訪問修飾符 默認(rèn)public] [主構(gòu)造函數(shù)] [參數(shù)] [: 父類 默認(rèn)為 Any] [類體]
定義類铐然,我們通過下面的例子來說明:
class EmptyClass
println(EmptyClass() is Any)
NOTE: [] 代表可以省略. Kotliin 中修飾符 與Java 略不同蔬崩,Java語言提供了很多修飾符,主要分為以下兩類:
- 訪問修飾符
- 非訪問修飾符
更詳細(xì)的 Java 修飾符 請參考
Java 修飾符 _ 菜鳥教程
Kotliin 中沒顯式聲明修飾符 ,默認(rèn)可見性是 public
搀暑。
訪問控制修飾符
類沥阳、對(duì)象、接口自点、構(gòu)造函數(shù)桐罕、方法、屬性和它們的 setter 都可以有 可見性修飾符桂敛。 (getter 總是與屬性有著相同的可見性功炮。) 在 Kotlin 中有這四個(gè)可見性修飾符:private
、 protected
术唬、 internal
和 public
薪伏。
修飾符 | 是否支持頂級(jí)聲明 | 當(dāng)前文件 | 同一模塊 |
---|---|---|---|
private | Y | Y | N |
protected | N | ~~~~ | ~~~~ |
internal | Y | Y | Y |
public | Y | Y | Y |
NOTE:
protected
不支持頂級(jí)聲明,因?yàn)槲募]有繼承關(guān)系。internal
是編譯在一起的一套 Kotlin 文件:- 一個(gè) IntelliJ IDEA 模塊粗仓;
- 一個(gè) Maven 項(xiàng)目嫁怀;
- 一個(gè) Gradle 源集;
- 一次 <kotlinc> Ant 任務(wù)執(zhí)行所編譯的一套文件借浊。
對(duì)于類和接口內(nèi)部聲明的成員可見修飾符與Java 類似:
-
private
僅該類和接口內(nèi)部可見塘淑; -
protectd
該類和接口內(nèi)部可見且子類可見 -
internal
該模塊內(nèi) 可見 -
public
都可見
-
非訪問控制修飾符
kotlin 定義類、對(duì)象巴碗、構(gòu)造函數(shù)朴爬、方法、屬性時(shí)默認(rèn)加了 final
修飾符, 接口默認(rèn)是 open
與之相反橡淆。能被繼承召噩、被覆蓋母赵。
NOTE:在 final 修飾 class 下 用 open 修飾該類的成員無效,在 final 缺省修飾符下 再用 final 修飾顯得 Redundant
冗余具滴,但在 override
時(shí)可使用final
關(guān)鍵字再度修飾
我們通過下面的例子來說明:
open class Father {
private val name = "嗶嗶" //private can't open
protected open val bloodType = "AB"
internal val number = 1000
open val age = 28
protected class Nested {
val body = {}
private val cipher = null
private fun print() {
//can't access private
// println(name)
// println(bloodType)
// println(number)
// println(age)
body
}
}
open fun print() {
println(name) //can't access private
println(bloodType)
println(number)
println(age)
Nested().body
// Nested().cipher//Kotlin 中外部類不能訪問內(nèi)部類的 private 成員
}
}
class Son : Father() {
override final val bloodType: String = "O" //protected // final Redundant
// override public val bloodType: String = "O" // 能覆蓋
override val age: Int = 10 // public
override open fun print() { //Warning: 'open' has no effect in a final class
// println(name) //can't access private
println(bloodType)
println(number)
println(age)
Nested().body
}
}
open class BigSon : Father() {
override final val bloodType: String = "AB" //can use final
}
NOTE:局部變量凹嘲、函數(shù)和類不能有可見性修飾符。Kotlin 中外部類不能訪問內(nèi)部類的 private 成員(與Java不同)构韵。
類成員
類可以包含
構(gòu)造函數(shù)
一個(gè)類可以有一個(gè)主構(gòu)造函數(shù)和一個(gè)或多個(gè)次構(gòu)造函數(shù)周蹭。主構(gòu)造函數(shù)是類頭的一部分:它跟在類名(和訪問修飾符 [默認(rèn) public])后。主構(gòu)造函數(shù)有注解或可見性修飾符疲恢,這個(gè) constructor 關(guān)鍵字是必需的凶朗,并且這些修飾符在它前面。非抽象類沒有聲明任何(主或次)構(gòu)造函數(shù)显拳,它會(huì)有一個(gè)生成的不帶參數(shù)的主構(gòu)造函數(shù)棚愤。構(gòu)造函數(shù)的可見性是 public。
NOTE:若要修改主構(gòu)造函數(shù)的可見性,需要添加一個(gè)顯式 constructor 關(guān)鍵字
class A private constructor() { …… }
Kotlin 十分簡便, 可以在主構(gòu)造函數(shù)內(nèi)聲明屬性(可變的(var)或只讀的(val))以及初始化屬性默認(rèn)值(次構(gòu)造函數(shù)是不允許的), 且為該類成員屬性, 主構(gòu)造函數(shù)內(nèi)不能包含除了聲明屬性任何的代碼杂数。提供了 init 關(guān)鍵字作為前綴的初始化塊(initializer blocks)宛畦。
次構(gòu)造函數(shù)
聲明在類體內(nèi)以 constructor
關(guān)鍵字的函數(shù)。若該類有主構(gòu)造函數(shù),次構(gòu)造函數(shù)都需要用 this
關(guān)鍵字直接或間接委托給主構(gòu)造函數(shù)揍移。
open class Person /*private*/ constructor(firstName: String) {
class A //empty class 下面接著是次構(gòu)造函數(shù) 次和,Error: Expecting member declaration, 期待成員聲明
val money = 1000_000
init {
println("init block: firstName= $firstName")
println("init block: money= $money")
}
//次構(gòu)造函數(shù)
constructor(firstName: String, age: Int) : this(firstName) {
println("secondary constructor: firstName= $firstName")
println("secondary constructor: age= $age")
println("init block: money= $money")
}
constructor (firstName: String, age: Int, money: Int) : this(firstName, age) {
println("secondary constructor: firstName= $firstName")
println("secondary constructor: age= $age")
println("init block: money= $money")
}
}
注意:在 JVM 上,如果主構(gòu)造函數(shù)的所有的參數(shù)都有默認(rèn)值那伐,編譯器會(huì)生成 一個(gè)額外的無參構(gòu)造函數(shù)踏施,它將使用默認(rèn)值。這使得 Kotlin 更易于使用像 Jackson 或者 JPA 這樣的通過無參構(gòu)造函數(shù)創(chuàng)建類的實(shí)例的庫喧锦。
class Customer(val customerName: String = "")
創(chuàng)建類的實(shí)例
Kotlin 并不需要 new 關(guān)鍵字創(chuàng)建實(shí)例, 像普通函數(shù)一樣調(diào)用構(gòu)造函數(shù)即可读规。
繼承
Java 的超類是 Object
, 而 Kotlin 的是 Any。
若父類有主構(gòu)造函數(shù)且?guī)?shù),子類必須用主構(gòu)造函數(shù)將參數(shù)初始化燃少,如下:
class Student(firstName: String) : Person(firstName) {
}
注意:參數(shù)初始化時(shí),子父類必須一致束亏。
父類沒有主構(gòu)造函數(shù), 那么每個(gè)次構(gòu)造函數(shù)必須使用 super 關(guān)鍵字初始化其基類型。
open class Human {
constructor(name: String) {
}
constructor(name: String, age: Int) {
}
}
class Woman : Human {
constructor(name: String) : super(name)
constructor(name: String, age: Int) : super(name, age)
}
//允許通過主構(gòu)造函數(shù)覆蓋次構(gòu)造函數(shù)
class Man(name: String) : Human(name)
覆蓋(override)
final
, open
是否可覆蓋修飾符 和 override
標(biāo)注覆蓋類阵具、對(duì)象碍遍、接口、構(gòu)造函數(shù)阳液、方法怕敬、屬性。
覆蓋規(guī)則
在 Kotlin 中帘皿,實(shí)現(xiàn)繼承由下述規(guī)則規(guī)定:如果一個(gè)類從它的直接超類繼承相同成員的多個(gè)實(shí)現(xiàn)东跪, 它必須覆蓋這個(gè)成員并提供其自己的實(shí)現(xiàn)(也許用繼承來的其中之一)來消除歧義。 為了表示采用 從哪個(gè)超類型繼承的實(shí)現(xiàn),我們使用由尖括號(hào)中超類型名限定的 super 虽填,如 super<Base> :
open class Thread {
open fun run() {
println("Thread#run")
}
fun start() {
println("Thread#start")
}
}
interface Runnable {
fun run() {
println("Thread#run")
} // 接口成員默認(rèn)就是“open”的
}
class HandlerThread() : Runnable, Thread() {
//編譯器要求覆蓋 run():
override fun run() {
super<Thread>.run() // 調(diào)用 Thread.run()
super<Runnable>.run() // 調(diào)用 Runnable.run()
}
}
抽象類
類和其中的某些成員可以聲明為 abstract 丁恭。 抽象成員在本類中可以不用實(shí)現(xiàn)。 需要注意的是斋日,我們并不需要用 open 標(biāo)注一個(gè)抽象類或者函數(shù)——因?yàn)檫@不言而喻牲览。
abstract class AbstractClass{ //open 多余的,因?yàn)槌橄箢惤K究是父類,所以更不能用final 修飾
open fun doSomething() {
}
abstract fun fly() //子類必須 override
}
class AbstractClassImpl : AbstractClass() {
override fun fly() {
}
override fun doSomething() {//override 開放成員
super.doSomething()
}
}
接口
用關(guān)鍵字 interface 來定義接口。Kotlin 的接口函數(shù)可以有實(shí)現(xiàn), 屬性必須是抽象的(默認(rèn)抽象), 或者提供 get
訪問器實(shí)現(xiàn), 且不能有幕后字段(backing field)恶守。
fun main(args: Array<String>) {
val kotlinLanguage = KotlinLanguage()
println(kotlinLanguage.language)
println(kotlinLanguage.that)
println(kotlinLanguage.that === kotlinLanguage)
kotlinLanguage.onReady()
kotlinLanguage.onUpgrade()
MultipurposePrinter().print()
}
interface KotlinInterface {
val language get() = "Kotlin"
val that: KotlinInterface
fun onUpgrade() {
println("call#onUpgrade")
}
fun onReady() //
}
class KotlinLanguage : KotlinInterface {
override val that: KotlinInterface
get() = this
override fun onReady() {
println("call#onReady")
}
}
interface Printer {
fun print()
}
interface ColorPrinter : Printer {
override fun print() {
println("ColorPrinter#print")
}
// val printerType get() = "ColorPrinter"
}
interface BlackPrinter : Printer {
override fun print() {
println("BlackPrinter#print")
}
val printerType get() = "BlackPrinter"
}
class MultipurposePrinter : ColorPrinter, BlackPrinter {
override fun print() {
println("MultipurposePrinter#print")
super<BlackPrinter>.print()
super<ColorPrinter>.print()
super.printerType
}
}
嵌套類和內(nèi)部類
類可以嵌套在其他類中
fun main(args: Array<String>) {
println(KotlinNestedInnerClass.KotlinNestedClass().bra())
println(KotlinNestedInnerClass().KotlinInnerClass().bra())
println(KotlinNestedInnerClass().KotlinInnerClass().reference())
}
private class KotlinNestedInnerClass {
private val bra: String = "C"
class KotlinNestedClass {
fun bra() = KotlinNestedInnerClass().bra
}
//內(nèi)部類 標(biāo)記為 inner 以便能夠訪問外部類的成員第献。內(nèi)部類會(huì)帶有一個(gè)對(duì)外部類的對(duì)象的引用
inner class KotlinInnerClass {
fun bra() = bra
fun reference() = this@KotlinNestedInnerClass //This 表達(dá)式
}
//匿名內(nèi)部類 @see 對(duì)象聲明(object)
}
如果對(duì)象是函數(shù)式 Java 接口(即具有單個(gè)抽象方法的 Java 接口)的實(shí)例乾巧, 你可以使用帶接口類型前綴的lambda表達(dá)式創(chuàng)建它:
val run = Runnable { }
對(duì)象(object)
在Java 中, 匿名內(nèi)部類隨處可見庵楷。然而 Kotlin 用 object
關(guān)鍵字提供了對(duì)象聲明以及對(duì)象表達(dá)式特性, 創(chuàng)建單例骂维、匿名對(duì)象, 伴生對(duì)象(類內(nèi)部的對(duì)象聲明) so easy廓推。
val point = object /*: Any()*/ { //默認(rèn)繼承 Any
var x: Int = 0 //必須進(jìn)行初始化
var y: Int = 0
override fun toString(): String {
return "point[$x,$y]"
}
}
point.x = 100
point.y = 300
println(point)
val singleton = Singleton
val singleton1 = Singleton
println(singleton === singleton1)
//對(duì)象聲明
object Singleton { //決不能聲明局部作用域(函數(shù)中)
}
NOTE: 如何區(qū)分對(duì)象聲明和對(duì)象表達(dá)式, 顧名思義, 有名字的是對(duì)象聲明(object Singleton), 沒名字的是對(duì)象表達(dá)式(anonymous object)圈纺。
關(guān)于 object
使用細(xì)節(jié),下面通過一個(gè)簡單例子為大家演示:
class KotlinObject {
private fun privateObject() = object { //返回: <anonymous object : Any>
val name = "123"
}
fun publicObject() = object { // 返回Any 建議private
val name = "ABC"
}
fun run() {
println(privateObject().name)
//println(publicObject().name) //錯(cuò)誤:未能解析的引用“name”
var visible = true
call(object : CallBack {
override fun call() {
visible //對(duì)象表達(dá)式中的代碼可以訪問來自包含它的作用域的變量
println("Anonymous#call@${this.hashCode()}")
}
})
call (object : CallBack {
override fun call() {
visible //對(duì)象表達(dá)式中的代碼可以訪問來自包含它的作用域的變量
println("Anonymous#call@${this.hashCode()}")
}
})
call(OneCallBack)
call(OneCallBack)
}
object OneCallBack : CallBack {
//因?yàn)閷?duì)象表達(dá)式不能綁定名字,這稱為對(duì)象聲明
override fun call() {
println("OneCallBack#call@${this.hashCode()}")
}
}
fun call(call: CallBack) {
call.call()
}
interface CallBack {
fun call(): Unit
}
}
fun main(args: Array<String>) {
KotlinObject().run()
}
私有函數(shù)時(shí),返回object
類型是匿名對(duì)象類型, 否則是 Any
蔽莱。與Java 不同內(nèi)部類也可訪問非 final 變量。對(duì)象聲明實(shí)則是單例橡伞。
伴生對(duì)象(companion object)
與 Java 或 C# 不同,在 Kotlin 中類沒有靜態(tài)方法晋被。在大多數(shù)情況下兑徘,它建議簡單地使用包級(jí)函數(shù)。
類內(nèi)部的對(duì)象聲明可以用 companion
關(guān)鍵字標(biāo)記:
open class World {
//Companion 是companion object 默認(rèn)名字可省略,僅且有一個(gè)伴生對(duì)象
companion object Companion : Observer {
@JvmField //@JvmField 標(biāo)注這樣的屬性使其成為與屬性本身具有相同可見性的靜態(tài)字段羡洛。
val time = System.nanoTime()
const val VERSION = "1.1.4.2" //kotlin 常量(const 標(biāo)注的(在類中以及在頂層的)屬性), 在 Java 中會(huì)成為靜態(tài)字段:
override fun update(o: Observable?, arg: Any?) {
}
// @JvmStatic //打開注釋編譯報(bào)錯(cuò),存在相同的函數(shù)聲明, 這充分地證明了伴生對(duì)象的成員看起來像其他語言的靜態(tài)成員挂脑,在運(yùn)行時(shí)他們?nèi)匀皇钦鎸?shí)對(duì)象的實(shí)例成員
fun sayHello() {
println("sayHello@${this.hashCode()} ")
}
}
fun sayHello() {
println("sayHello@${this.hashCode()} ")
}
}
fun main(args: Array<String>) {
World.sayHello()
World.Companion.sayHello()
World().sayHello()
}
Java 中調(diào)用
public class StaticTest {
public static void main(String[] args) {
System.out.println(World.Companion);
System.out.println(World.VERSION);
System.out.println(World.time);
}
}
NOTE:伴生對(duì)象實(shí)際是對(duì)象的實(shí)例成員, JVM 平臺(tái)欲侮,如果使用 @JvmStatic
注解崭闲,你可以將伴生對(duì)象的成員生成為真正的靜態(tài)方法和字段。更詳細(xì)信息請參見Java 互操作性一節(jié) 威蕉。
對(duì)象表達(dá)式和對(duì)象聲明之間有一個(gè)重要的語義差別:
- 對(duì)象表達(dá)式是在使用他們的地方立即執(zhí)行(及初始化)的
- 對(duì)象聲明是在第一次被訪問到時(shí)延遲初始化的
- 伴生對(duì)象的初始化是在相應(yīng)的類被加載(解析)時(shí)刁俭,與 Java 靜態(tài)初始化器的語義相匹配
數(shù)據(jù)類
我們經(jīng)常創(chuàng)建一些只保存數(shù)據(jù)的類。在這些類中韧涨,一些標(biāo)準(zhǔn)函數(shù)往往是從數(shù)據(jù)機(jī)械推導(dǎo)而來的牍戚。在 Kotlin 中,這叫做 數(shù)據(jù)類 并標(biāo)記為 data
:
fun main(args: Array<String>) {
val user1 = KotlinDataClass.User("小明", 19)
val user2 = KotlinDataClass.User("小明", 19)
println(user1 == user2)
println(user1)
val copyXiaoMing = user1.copy(age = 20)
println(copyXiaoMing)
println(user1.component1())
val bb = KotlinDataClass.User("bb")
println(bb)
//數(shù)據(jù)類和解構(gòu)聲明
val (name, age) = KotlinDataClass.User("Lisa", 18)
println("$name, $age years of age")
//標(biāo)準(zhǔn)數(shù)據(jù)類
val anPair: Pair<Char, Char> = Pair('A', 'B')
println("first = ${anPair.first}, second = ${anPair.second}")
val (a,b,c) = Triple('A','B','C')
println("($a, $b, $c)")
}
private class KotlinDataClass {
open class Person
//數(shù)據(jù)類本身是 final,必須有主構(gòu)造器,至少一個(gè)參數(shù)
data class User(val name: String, val age: Int = 0) : Person() {
//編譯器會(huì)根據(jù)主構(gòu)造函數(shù)的參數(shù)生成以下函數(shù),根據(jù)需求 override
// override fun equals(other: Any?): Boolean {
// return super.equals(other)
// }
//
// override fun hashCode(): Int {
// return super.hashCode()
// }
//
// override fun toString(): String {
// return super.toString()
// }
// Error: Conflicting overloads:
// fun component1(){
//
// }
}
}
編譯器自動(dòng)從主構(gòu)造函數(shù)中聲明的所有屬性導(dǎo)出以下成員:
-
equals()
/hashCode()
對(duì)虑粥, -
toString()
格式是"User(name=John, age=42)"
如孝, -
componentN()
函數(shù) 按聲明順序?qū)?yīng)于所有屬性, -
copy()
函數(shù), 復(fù)制一個(gè)對(duì)象僅改變某些屬性娩贷。
為了確保生成的代碼的一致性和有意義的行為第晰,數(shù)據(jù)類必須滿足以下要求:
- 主構(gòu)造函數(shù)需要至少有一個(gè)參數(shù);
- 主構(gòu)造函數(shù)的所有參數(shù)需要標(biāo)記為
val
或var
; - 數(shù)據(jù)類不能是抽象茁瘦、開放罗岖、密封或者內(nèi)部的;
- (在1.1之前)數(shù)據(jù)類只能實(shí)現(xiàn)接口腹躁。
自 1.1 起桑包,數(shù)據(jù)類可以擴(kuò)展其他類(示例請參見密封類)。
在 JVM 中纺非,如果生成的類需要含有一個(gè)無參的構(gòu)造函數(shù)哑了,則所有的屬性必須指定默認(rèn)值。 (參見構(gòu)造函數(shù))烧颖。
密封類
密封類用來表示受限的類繼承結(jié)構(gòu):當(dāng)一個(gè)值為有限集中的類型弱左、而不能有任何其他類型時(shí)。在某種意義上炕淮,他們是枚舉類的擴(kuò)展:枚舉類型的值集合也是受限的拆火,但每個(gè)枚舉常量只存在一個(gè)實(shí)例,而密封類的一個(gè)子類可以有可包含狀態(tài)的多個(gè)實(shí)例涂圆。
NOTE: sealed
不能修飾 interface ,abstract class(會(huì)報(bào) warning,但是不會(huì)出現(xiàn)編譯錯(cuò)誤)
fun main(args: Array<String>) {
val kotlinSealedClass = ChildrenKotlinSealedClass()
println(eval(kotlinSealedClass))
}
sealed class KotlinSealedClass
class ChildrenKotlinSealedClass : KotlinSealedClass()
class GirlKotlinSealedClass : KotlinSealedClass()
private fun eval(k: KotlinSealedClass): String = when (k) {
is ChildrenKotlinSealedClass -> "ChildrenKotlinSealedClass"
is GirlKotlinSealedClass -> "GirlKotlinSealedClass"
//不再需要 else 分支 已經(jīng)覆蓋了所有的情況
}
枚舉類
枚舉類的最基本的用法是實(shí)現(xiàn)類型安全的枚舉, 每個(gè)枚舉常量都是一個(gè)對(duì)象, 需用逗號(hào)分開们镜。示例如下
fun main(args: Array<String>) {
for (it in KotlinEnumClass.Direction.values()) {
println(it)
}
//必須與聲明枚舉類型名稱一致, 否則拋出 IllegalArgumentException 異常。
val north = KotlinEnumClass.Direction.valueOf("NORTH")
println(north === KotlinEnumClass.Direction.NORTH)
//枚舉常量都具有在枚舉類聲明中獲取其名稱和位置的屬性
val (name, ordinal) = KotlinEnumClass.Direction.EAST
println("$name $ordinal")
KotlinEnumClass().printAllValues<KotlinEnumClass.ProtocolState>()
println()
KotlinEnumClass().printValue<KotlinEnumClass.ProtocolState>("WAITING")
}
private class KotlinEnumClass {
//類型安全的枚舉
enum class Direction {
NORTH, SOUTH, WEST, EAST;
}
//枚舉都是枚舉類的實(shí)例,可以初始化
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
//枚舉常量也可以聲明自己的匿名類
enum class ProtocolState {
WAITING {
override fun signal() = TALKING
},
TALKING {
override fun signal() = WAITING
};
abstract fun signal(): ProtocolState
}
//列出定義的枚舉常量
inline fun <reified T : Enum<T>> printAllValues() {
print(enumValues<T>().joinToString { it.name })
}
//通過名稱獲取枚舉常量
inline fun <reified T : Enum<T>> printValue(name: String) {
print(enumValueOf<T>(name))
}
}
枚舉常量還實(shí)現(xiàn)了 Comparable 接口润歉, 其中自然順序是它們在枚舉類中定義的順序模狭。
NOTE: val (name, ordinal) = KotlinEnumClass.Direction.EAST
之所以可以編譯通過, 因?yàn)槲覍?duì)枚舉類進(jìn)行解構(gòu)聲明
//學(xué)而致用
operator fun <E : Enum<E>> Enum<E>.component1() = this.name
operator fun <E : Enum<E>> Enum<E>.component2() = this.ordinal
注解類
學(xué)習(xí)Java 的應(yīng)該對(duì)注解不陌生,不了解可以先看看 Java的注解。
注解聲明
[訪問修飾符 默認(rèn)public] [非訪問修飾符 默認(rèn)只能為 final 不能顯式修飾] annotation class 類名
[訪問修飾符 只能為public] [主構(gòu)造函數(shù) constructor 關(guān)鍵字可有可無] [val參數(shù)]
internal annotation class KotlinFileName(val name:String)
允許的參數(shù)類型有:
- 對(duì)應(yīng)于 Java 原生類型的類型(Int踩衩、 Long等)以及字符串
- 類
KClass
嚼鹉、枚舉 - 其他注解
- 上面已列類型的數(shù)組
NOTE: 注解參數(shù)不能有可空類型,因?yàn)?JVM 不支持將 null
作為注解屬性的值存儲(chǔ)驱富。如果注解用作另一個(gè)注解的參數(shù)锚赤,則其名稱不以 @ 字符為前綴, 且新的注解類訪問權(quán)限不能比其中一個(gè)注解的參數(shù)的訪問權(quán)限要大
internal annotation class FileScope
constructor(@ApplicationScope val file: KotlinFileName)
注解的附加屬性可以通過用元注解標(biāo)注注解類來指定:
-
@Target
指定可以用該注解標(biāo)注的元素的可能的類型(類、函數(shù)褐鸥、屬性线脚、表達(dá)式等); -
@Retention
指定該注解是否存儲(chǔ)在編譯后的 class 文件中晶疼,以及它在運(yùn)行時(shí)能否通過反射可見 (默認(rèn)都是 true)酒贬; -
@Repeatable
允許在單個(gè)元素上多次使用相同的該注解; -
@MustBeDocumented
指定該注解是公有 API 的一部分翠霍,并且應(yīng)該包含在生成的 API 文檔中顯示的類或方法的簽名中锭吨。
@Target(AnnotationTarget.CLASS,
AnnotationTarget.FILE,
AnnotationTarget.FUNCTION,
AnnotationTarget.VALUE_PARAMETER,
AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
annotation private class ApplicationScope
Lambda 表達(dá)式
注解也可以用于 lambda 表達(dá)式。它們會(huì)被應(yīng)用于生成 lambda 表達(dá)式體的 invoke()
方法上寒匙。
annotation class Anonymous
val run = @KotlinAnnotation.Anonymous { println("run") }
Use-site Targets (使用處 目標(biāo))
當(dāng)對(duì)屬性或主構(gòu)造函數(shù)參數(shù)進(jìn)行標(biāo)注時(shí)零如,從相應(yīng)的 Kotlin 元素生成的 Java 元素會(huì)有多個(gè)躏将,因此在生成的 Java 字節(jié)碼中該注解有多個(gè)可能位置 。支持的使用處目標(biāo)的完整列表為:
file
-
property
(具有此目標(biāo)的注解對(duì) Java 不可見) field
-
get
(屬性 getter) -
set
(屬性 setter) -
receiver
(擴(kuò)展函數(shù)或?qū)傩缘慕邮照邊?shù)) -
param
(構(gòu)造函數(shù)參數(shù)) -
setparam
(屬性 setter 參數(shù)) -
delegate
(為委托屬性存儲(chǔ)其委托實(shí)例的字段)
可以使用相同的語法來注釋整個(gè)文件考蕾。要執(zhí)行此操作祸憋,請將目標(biāo)文件的注釋放在文件的頂層,在包指令之前或在所有導(dǎo)入之前肖卧,如果文件位于默認(rèn)包中:
@file:JvmName("KotlinAnnotationKt")
package demo
如果要指定精確地指定應(yīng)該如何生成該注解蚯窥,請使用以下語法:
@處目標(biāo)元素:[注解A 注解B ] ... //同一目標(biāo)只有1個(gè)注解時(shí)方括號(hào)可以省略
簡單示例如下:
class User(@field:FieldScope val name: String, @get:[ApplicationScope FunScope] val age: Int)
如果不指定使用處目標(biāo),則根據(jù)正在使用的注解的 @Target
注解來選擇目標(biāo) 塞帐。
Java 注解
Java 注解與 Kotlin 100% 兼容:
kotlin
//聲明注解
annotation class Targets(vararg val value: KClass<*>)
annotation class TargetArrays(val value: Array<KClass<*>>)
@JavaAnnotation.Describe("see")
class See
@JavaAnnotation.SinceJava(name = "jdk", version = 1_8_0)
class JDK
@JavaAnnotation.Targets(Any::class, String::class)
class Targets
@JavaAnnotation.Targets(*arrayOf(Any::class, String::class))
class Targets2
fun printId(intId: JavaAnnotation.IntId) {
println(intId.value)
}
@JavaAnnotation.IntId(Int.MAX_VALUE)
class Res
printId(Res::class.annotations[0] as JavaAnnotation.IntId)
java
@KotlinAnnotation.ApplicationScope
public class JavaAnnotation {
public static void main(String[] args) {
try {
Class clazz = Class.forName("jsource.JavaAnnotation");
Annotation annotation = clazz.getAnnotation(KotlinAnnotation.ApplicationScope.class);
System.out.println(annotation);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
@KotlinAnnotation.Targets({String.class, Integer.class})
class TargetClass {
}
@KotlinAnnotation.TargetArrays({String.class, Integer.class})
class TargetArrays {
}
public @interface Describe {
String value();
}
public @interface SinceJava {
String name();
int version();
}
public @interface Targets {
Class[] value();
}
@Retention(RetentionPolicy.RUNTIME)
public @interface IntId {
int value() default -1;
}
}
泛型
與 Java 類似拦赠,Kotlin 中的泛型,如下示例:
fun main(args: Array<String>) {
val emptyListString = List<String>()
val listString = List("C", "D")
assertEquals(0, emptyListString.size, "empty")
printList(listString)
}
//泛型類
private class List<T>(vararg elements: T) : Iterable<T> {
override fun iterator(): Iterator<T> {
return elementsArray.iterator()
}
val elementsArray = mutableListOf(*elements)
operator fun get(index: Int): T = elementsArray[index]
val size: Int = elementsArray.size
}
// 泛型方法 printList
private fun <E> printList(inputList: List<E>) {
for (element in inputList) {
println("$element ")
}
println()
}
與 Java 不同,Kotlin 中的泛型沒有通配符類型葵姥,它有:聲明處型變(declaration-site variance)與類型投影(type projections)荷鼠。
型變
Java 中的泛型是不型變的,這意味著 List<String>
并不是List<Object>
的子類型榔幸。
List<String> strs = new ArrayList<String>();
List<Object> objs =(List) strs;
objs.add(1);
String s = strs.get(0); // T世帧!削咆! ClassCastException:無法將整數(shù)轉(zhuǎn)換為字符串
PECS原則,在Java <? extends T>牍疏、<? super T>
通配符類型參數(shù),前者只能讀取, 不能寫入,后者反之。便有一條規(guī)律态辛,”Producer Extends, Consumer Super”:
-
Producer Extends
– 如果你需要一個(gè)只讀List麸澜,用它來produce T,那么使用? extends T
奏黑。 -
Consumer Super
– 如果你需要一個(gè)只寫List惹想,用它來consume T涨共,那么使用? super T
。 - 如果需要同時(shí)讀取以及寫入类垦,那么我們就不能使用通配符了窄俏。
同樣PECS原則適用于 Kotlin:
-
Producer Extends
– 使得類型是協(xié)變的(covariant)蹂匹。 -
Consumer Super
– 使得類型是逆變性(contravariance)。
NOTE: PECS 代表生產(chǎn)者-Extens凹蜈,消費(fèi)者-Super(Producer-Extends, Consumer-Super)限寞。一個(gè)生產(chǎn)者對(duì)象,只是保證類型安全仰坦。
聲明處型變
Java 中List<String>
不能直接賦值List<Object>
,在 Kotlin 中履植,提供 out 修飾符確保接口或類成員中返回out
(生產(chǎn)),并從不被 in
(消費(fèi))悄晃。
val stringList = listOf<String>()
val anyList: List<Any> = stringList
kotlin List 接口聲明:
public interface List<out E> : Collection<E>
in玫霎。它使得一個(gè)類型參數(shù)逆變:只可以被消費(fèi)而不可以被生產(chǎn)。逆變類的一個(gè)很好的例子是 Comparable
:
abstract class Comparable<in T> {
abstract fun compareTo(other: T): Int
}
fun demo(x: Comparable<Number>) {
x.compareTo(1.0) // 1.0 擁有類型 Double,它是 Number 的子類型
// 因此庶近,我們可以將 x 賦給類型為 Comparable <Double> 的變量
val y: Comparable<Double> = x // OK翁脆!
}
類型參數(shù) T
被聲明為 out 時(shí),雖然 **<Base>
可以安全地作為 **<Derived>
的超類, 就只能出現(xiàn)輸出-位置鼻种。
因?yàn)樗陬愋蛥?shù)聲明處提供反番,所以被稱做聲明處型變。 這與 Java 的使用處型變相反叉钥,其類型用途通配符使得類型協(xié)變罢缸。in 反之。
**NOTE:消費(fèi)者 in, 生產(chǎn)者 out **
類型投影 (使用處型變)
將類型參數(shù) T 聲明為 out 非常方便沼侣,并且能避免使用處子類型化的麻煩祖能,但是有些類實(shí)際上不能限制為只返回 T
比如 Array:
val ints: Array<out Int> = arrayOf(1, 2, 3)
val any = Array<Any>(3) { "" }
//out 生產(chǎn)者 相當(dāng)于Java ? extends T
fun copy(from: Array<out Any>, to: Array<Any>) {
for (index in from.indices) {
to[index] = from[index]
}
}
copy(from = ints, to = any)
for (items in any) {
println(items)
}
//out 消費(fèi)者 相當(dāng)于Java ? super T
fun fill(dest: Array<in Int>, value: Int) {
for (index in dest.indices) {
dest[index] = (dest[index] as? Int)!!.times(value)
}
}
fill(any, 2)
for (items in any) {
println(items)
}
上面out in 類型投影, 也就是Java 的使用處型變 ? [extends][super] T
星投影
若類型參數(shù)一無所知,但仍然希望以安全的方式使用它蛾洛。 這里的安全方式是定義泛型類型的這種投影养铸,該泛型類型的每個(gè)具體實(shí)例化將是該投影的子類型。
Kotlin 為此提供了所謂的星投影語法:
val star: List<*> = listOf("C", "D", 1, 2)
val any1: Any? = star[0]
fun compareTo2(x: Comparable<*>)
- 對(duì)于
Foo <out T>
轧膘,其中T
是一個(gè)具有上界TUpper
的協(xié)變類型參數(shù)钞螟,Foo <*>
等價(jià)于Foo <out TUpper>
。 這意味著當(dāng)T
未知時(shí)谎碍,你可以安全地從Foo <*>
讀取TUpper
的值鳞滨。 - 對(duì)于
Foo <in T>
,其中T
是一個(gè)逆變類型參數(shù)蟆淀,Foo <*>
等價(jià)于Foo <in Nothing>
拯啦。 這意味著當(dāng)T
未知時(shí),沒有什么可以以安全的方式寫入Foo <*>
熔任。 - 對(duì)于
Foo <T>
褒链,其中T
是一個(gè)具有上界TUpper
的不型變類型參數(shù),Foo<*>
對(duì)于讀取值時(shí)等價(jià)于Foo<out TUpper>
而對(duì)于寫值時(shí)等價(jià)于Foo<in Nothing>
疑苔。
如果泛型類型具有多個(gè)類型參數(shù)甫匹,則每個(gè)類型參數(shù)都可以單獨(dú)投影。 例如惦费,如果類型被聲明為 interface Function <in T, out U>
兵迅,我們可以想象以下星投影:
-
Function<*, String>
表示Function<in Nothing, String>
; -
Function<Int, *>
表示Function<Int, out Any?>
薪贫; -
Function<*, *>
表示Function<in Nothing, out Any?>
恍箭。
注意:星投影非常像 Java 的原始類型,但是安全后雷。
泛型約束
能夠替換給定類型參數(shù)的所有可能類型的集合可以由泛型約束限制季惯。
最常見的約束類型是與 Java 的 extends 關(guān)鍵字對(duì)應(yīng)的 上界:
fun <T : Number> add(t: T) {
// ……
}
add(1)
add("") //not allow
默認(rèn)的上界(如果沒有聲明)是 Any?
吠各。在尖括號(hào)中只能指定一個(gè)上界。 如果同一類型參數(shù)需要多個(gè)上界勉抓,我們需要一個(gè)單獨(dú)的 where-子句:
fun <T> cloneWhenGreater(t: T)
where T : Number,
// T : String, 只指定一個(gè)class ,接口可以多個(gè)
T : kotlin.Comparable<T>,
T : Cloneable {
}
擴(kuò)展
Kotlin 同 C# 和 Gosu 類似贾漏,能夠擴(kuò)展一個(gè)類的新功能而無需繼承該類或使用像裝飾者這樣的任何類型的設(shè)計(jì)模式。 這通過叫做 擴(kuò)展 的特殊聲明完成藕筋。Kotlin 支持 擴(kuò)展函數(shù) 和 擴(kuò)展屬性纵散。
擴(kuò)展函數(shù)和屬性
聲明一個(gè)擴(kuò)展函數(shù)和屬性,我們需要用一個(gè) 接收者類型 也就是被擴(kuò)展的類型來作為他的前綴隐圾。
class KotlinExtension {
//成員函數(shù)比擴(kuò)展函數(shù)優(yōu)先
fun member() {
println("call#member")
}
fun fileName(): String {
return "KotlinExtension.class"
}
companion object
}
//擴(kuò)展的對(duì)象類型 KotlinExtension
fun KotlinExtension.extensionFun() {
println("this@${this} call#extensionFun") //
}
fun KotlinExtension.member() {
println("call#extension") //
}
//接收者類型表達(dá)式中使用泛型 要在函數(shù)名前聲明泛型參數(shù)
fun <E> List<E>.addAll(){
//...
}
//擴(kuò)展屬性(Extension Property) 實(shí)際擴(kuò)展get* 函數(shù)而已
val KotlinExtension.fileName
get() = "KotlinExtension.kt"
NOTE: this 關(guān)鍵字在擴(kuò)展函數(shù)內(nèi)部對(duì)應(yīng)到接收者對(duì)象(傳過來的在點(diǎn)符號(hào)前的對(duì)象)
可空接收者
可空的接收者類型也能定義擴(kuò)展伍掀,在對(duì)象變量上調(diào)用值為 null時(shí),并且可以在函數(shù)體內(nèi)檢測 this == null
暇藏,
檢測發(fā)生在擴(kuò)展函數(shù)的內(nèi)部蜜笤。最好的例子,如 Library.kt中:
public fun Any?.toString(): String
伴生對(duì)象的擴(kuò)展
伴生對(duì)象的擴(kuò)展和定義擴(kuò)展函數(shù)和屬性一致:
val KotlinExtension.Companion.anProperty: Int get() = 1
fun KotlinExtension.Companion.extensionFun() {
println("call#Companion.extensionFun")
}
擴(kuò)展的作用域
大多數(shù)在頂層定義擴(kuò)展,要使用所定義包之外的一個(gè)擴(kuò)展盐碱,導(dǎo)包就可以使用它把兔。類內(nèi)部也可以聲明擴(kuò)展(我認(rèn)為這并無卵用)在這樣的擴(kuò)展內(nèi)部,該類的對(duì)象和接收者的對(duì)象成員瓮顽,自由訪問县好。擴(kuò)展聲明所在的類的實(shí)例稱為 分發(fā)接收者,擴(kuò)展方法調(diào)用所在的接收者類型的實(shí)例稱為 擴(kuò)展接收者暖混。
class KotlinInteriorExtension {
fun start() {
println("call#start")
}
fun KotlinExtension.stop(){
start()
member() //擴(kuò)展聲明為成員時(shí) 擴(kuò)展函數(shù)優(yōu)先
this@KotlinInteriorExtension.member() //使用 限定this
}
fun member() {
println("call#member")
}
}
擴(kuò)展是靜態(tài)解析的
謹(jǐn)記擴(kuò)展不能真正的修改他們所擴(kuò)展的類缕贡, 僅僅是可以通過該類型的變量用點(diǎn)表達(dá)式去調(diào)用這個(gè)新函數(shù)。
擴(kuò)展函數(shù)是靜態(tài)分發(fā)的拣播,是由函數(shù)調(diào)用所在的表達(dá)式的類型來決定晾咪。
//擴(kuò)展是靜態(tài)解析的
open class LocalBookmark
class CloudBookmark : LocalBookmark()
open class LocalBookmarkManage {
open fun LocalBookmark.sync() {
println("syncToCloud")
}
open fun CloudBookmark.sync() {
println("syncFromCloud")
}
fun syncLocal(localBookmark: LocalBookmark) {
localBookmark.sync()
}
}
class CloudBookmarkManage : LocalBookmarkManage() {
override fun LocalBookmark.sync() {
println("syncFromLocal")
}
override fun CloudBookmark.sync() {
println("syncToLocal")
}
}
//run
LocalBookmarkManage().syncLocal(localBookmark) //輸出 syncToCloud
CloudBookmarkManage().syncLocal(cloudBookmark) //輸出 syncFromLocal —— 分發(fā)接收者虛擬解析
LocalBookmarkManage().syncLocal(cloudBookmark)//輸出 syncToCloud —— 擴(kuò)展接收者靜態(tài)解析
CloudBookmarkManage().syncLocal(localBookmark)//輸出 syncFromLocal —— 分發(fā)接收者虛擬解析
函數(shù)的分發(fā)對(duì)于分發(fā)接收者類型是虛擬的,但對(duì)于擴(kuò)展接收者類型一定是靜態(tài)的贮配。
委托
kotlin 支持委托類和屬性, 使用關(guān)鍵字 by
.
類委托
interface Printer {
fun print()
}
class ColorPrinter : Printer {
override fun print() {
println("ColorPrinter#print")
}
}
class BlackPrinter : Printer {
override fun print() {
println("BlackPrinter#print")
}
}
class MultipurposePrinter(val printer: Printer) : Printer by printer {
//可覆蓋 , 不覆蓋轉(zhuǎn)發(fā)printer print 方法
override fun print() {
printer.print()
println("override#print")
}
}
fun main(args: Array<String>) {
MultipurposePrinter(ColorPrinter()).print()
MultipurposePrinter(BlackPrinter()).print()
}
by xxa
-子句表示xxa
將會(huì)在 類中內(nèi)部存儲(chǔ)禀酱。 并且編譯器將生成轉(zhuǎn)發(fā)給 xxa
的所有成員函數(shù)。
委托屬性
kotlin 標(biāo)準(zhǔn)庫實(shí)現(xiàn)如下常見的屬性類型:
- 延遲屬性(lazy properties): 其值只在首次訪問時(shí)計(jì)算牧嫉,
- 可觀察屬性(observable properties): 監(jiān)聽器會(huì)收到有關(guān)此屬性變更的通知,
- 把多個(gè)屬性儲(chǔ)存在一個(gè)映射(map)中减途,而不是每個(gè)存在單獨(dú)的字段中酣藻。
延遲屬性 Lazy
lazy()
是接受一個(gè) lambda 并返回一個(gè) Lazy <T>
實(shí)例的函數(shù),返回的實(shí)例可以作為實(shí)現(xiàn)延遲屬性的委托: 第一次調(diào)用 get()
會(huì)執(zhí)行已傳遞給 lazy()
的 lambda 表達(dá)式并記錄結(jié)果鳍置, 后續(xù)調(diào)用 get()
只是返回記錄的結(jié)果辽剧。
默認(rèn)情況下,對(duì)于 lazy 屬性的求值是同步鎖的(synchronized):該值只在一個(gè)線程中計(jì)算税产,并且所有線程會(huì)看到相同的值怕轿。如果初始化委托的同步鎖不是必需的偷崩,這樣多個(gè)線程可以同時(shí)執(zhí)行,那么將 LazyThreadSafetyMode.PUBLICATION
作為參數(shù)傳遞給 lazy()
函數(shù)撞羽。 而如果你確定初始化將總是發(fā)生在單個(gè)線程阐斜,那么你可以使用 LazyThreadSafetyMode.NONE
模式蔚舀, 它不會(huì)有任何線程安全的保證和相關(guān)的開銷碟摆。
val lazyValue by lazy<String>(LazyThreadSafetyMode.SYNCHRONIZED) {
println("computed!")
"Hello" //同步鎖的(synchronized)
}
println(lazyValue)
println(lazyValue)
這個(gè)例子輸出:
computed!
Hello
Hello
可觀察屬性 Observable
Delegates.observable()
接受兩個(gè)參數(shù):初始值和修改時(shí)處理程序(handler)。 每當(dāng)我們給屬性賦值時(shí)會(huì)調(diào)用該處理程序(在賦值后執(zhí)行)沈自。它有三個(gè)參數(shù):被賦值的屬性邻奠、舊值和新值笤喳。
如果你想能夠截獲一個(gè)賦值并“否決”它,就使用 vetoable()
取代 observable()
碌宴。 在屬性被賦新值生效之前會(huì)調(diào)用傳遞給 vetoable
的處理程序杀狡。
var name by Delegates.observable("No Name") { prop, old, new ->
println("被賦值的屬性:${prop.name}, $old > $new")
}
name = "両儀式"
name = "式"
var skip by Delegates.vetoable("Null") { property, oldValue, newValue ->
println("被賦值的屬性:${property.name}, $oldValue > $newValue")
false
}
skip = "Test"
println(skip)
這個(gè)例子輸出:
被賦值的屬性:name, No Name > 両儀式
被賦值的屬性:name, 両儀式 > 式
被賦值的屬性:skip, Null > Test
Null
把屬性儲(chǔ)存在映射中
Map 可作為委托來實(shí)現(xiàn)委托屬性。
val languageMap = mapOf("language" to "kotlin")
val language by languageMap //變量名就是map的key 否則找不到該key Exception: NoSuchElementException
println(language)
若要 var
屬性只需要使用 MutableMap
贰镣。同樣也適用于類
class User(map: Map<String, Any?>) {
val name: String by map
val age: Int by map
fun make() {
println("make")
}
fun enable() = true
}
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
println("${user.name} ${user.age}")//ok
局部委托屬性
what? 看 lazy()
強(qiáng)大的初始化:
fun letMake(take: () -> User) {
val lazyUser by lazy(take)
//todo change true
if (false && lazyUser.enable()) {
lazyUser.make()
}
}
//...
letMake { ->
println("init")
User(mapOf("Twins" to 17))
}
自定義委托
var 屬性需要實(shí)現(xiàn) getValue()
setValue()
函數(shù),val 只是需要getValue()
即可呜象。兩函數(shù)都需要用 operator
關(guān)鍵字來進(jìn)行標(biāo)記。
委托類還可以實(shí)現(xiàn)包含所需 operator
方法的 ReadOnlyProperty
或 ReadWriteProperty
接口之一八孝。 這倆接口是在 Kotlin 標(biāo)準(zhǔn)庫中聲明的:
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name} in $thisRef.'")
}
}
class ReadDelegate : ReadOnlyProperty<Any?, String> {
override operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
//不需要 setValue
}
//test
var p: String by Delegate()
p = "default"
p = "$p \nchange"
val read by ReadDelegate()
println(read)
背后原理
在使用委托的時(shí), 不難發(fā)現(xiàn)該屬性是委托類型董朝。比如: p is String
,輸出false。
在每個(gè)委托屬性的實(shí)現(xiàn)的背后干跛,Kotlin 編譯器都會(huì)生成輔助屬性并委托給它子姜。 例如,對(duì)于屬性 prop
楼入,生成隱藏屬性 prop$delegate
哥捕,而訪問器的代碼只是簡單地委托給這個(gè)附加屬性:
class C {
var prop: Type by MyDelegate()
}
// 這段是由編譯器生成的相應(yīng)代碼:
class C {
private val prop$delegate = MyDelegate()
var prop: Type
get() = prop$delegate.getValue(this, this::prop)
set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}
Kotlin 編譯器在參數(shù)中提供了關(guān)于 prop
的所有必要信息:第一個(gè)參數(shù) this
引用到外部類 C
的實(shí)例而 this::prop
是 KProperty
類型的反射對(duì)象,該對(duì)象描述 prop
自身嘉熊。
提供委托
kotlin 提供 provideDelegate
操作符遥赚,可以擴(kuò)展創(chuàng)建屬性實(shí)現(xiàn)所委托對(duì)象的邏輯。使用場景是在創(chuàng)建屬性時(shí)(而不僅在其 getter 或 setter 中)檢查屬性一致性阐肤。
class R {
object id {
val textView = 0x003
val imageView = 0x004
}
object string {
val hello_world = 0x001
}
object drawable {
val icon_launch = 0x002
}
}
open class View(val id: Int)
open class ImageView(id: Int) : View(id)
open class TextView(id: Int, var text: String = "") : View(id)
class MyActivity {
val helloWorld by findResourceById<String>(R.string.hello_world)
val textView by findResourceById<TextView>(R.id.textView)
inline fun <reified T> findResourceById(id: Int): ResourceLoader<T> {
return ResourceLoader<T>(id)
}
fun draw() {
println(helloWorld)
textView.text = "Hello"
println(textView.text)
}
}
class ResourceLoader<out T>(val id: Int) {
operator fun provideDelegate(
thisRef: MyActivity,
prop: KProperty<*>
): ReadOnlyProperty<MyActivity, T> {
return ResDelegate<T>(id)
}
private class ResDelegate<out V>(val id: Int) : ReadOnlyProperty<MyActivity, V> {
val cacheKProperty = mutableMapOf<String, Any>()
override fun getValue(thisRef: MyActivity, property: KProperty<*>): V {
val last = cacheKProperty[property.name]
if (last != null) {
return last as V
}
val value = when (property.returnType.classifier) {
String::class -> property.name as V
View::class -> View(id) as V
TextView::class -> TextView(id) as V
ImageView::class -> ImageView(id) as V
else -> throw NoSuchElementException()
}
cacheKProperty.put(property.name, value!!)
return value
}
}
}
提供委托, 并不復(fù)雜凫佛。通過一個(gè)函數(shù)去獲取委托而已。provideDelegate
方法只影響輔助屬性的創(chuàng)建孕惜,并不會(huì)影響為 getter 或 setter 生成的代碼愧薛。
函數(shù)
函數(shù)用法
Kotlin 中的函數(shù)使用 fun
關(guān)鍵字聲明
fun funName(參數(shù))[: returnType(默認(rèn) Unit)] ...
函數(shù)參數(shù)規(guī)則
- 函數(shù)參數(shù)使用 Pascal 表示法定義,即 name: type , 參數(shù)用逗號(hào)隔開衫画。
- 每個(gè)參數(shù)必須有顯式類型, 參數(shù)還可以有默認(rèn)值毫炉,當(dāng)省略相應(yīng)的參數(shù)時(shí)使用默認(rèn)值, 以減少重載數(shù)量。
- 覆蓋帶有默認(rèn)參數(shù)值的方法時(shí)削罩,默認(rèn)參數(shù)值省略瞄勾。
- 如果一個(gè)默認(rèn)參數(shù)在一個(gè)無默認(rèn)值的參數(shù)之前费奸,那么該默認(rèn)值只能通過使用命名參數(shù)調(diào)用該函數(shù)來使用
- 如果最后一個(gè) lambda 表達(dá)式參數(shù)從括號(hào)外傳給函數(shù)函數(shù)調(diào)用,那么允許默認(rèn)參數(shù)不傳值
fun invoke(method: String, invoke: Any = this) {
println("call#method= $method $invoke")
}
fun invokeWithNameParameter(status: Int = 0, method: String, invoke: Any = this) {
println("call#method= $method $invoke")
}
fun invokeWithLambda(status: Int = 0, method: String = "invokeWithLambda", invoke: Any = this, apply: () -> Unit) {
println("call#method= $method $invoke")
}
abstract class Source {
abstract fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size)
}
class FileSource : Source() {
override fun read(b: Array<Byte>, off: Int, len: Int) {
println("b.length = ${b.size} off = $off len = $len")
}
}
//test
invoke("invoke")
invokeWithNameParameter(method = "invokeWithNameParameter")
invokeWithLambda(status = 1) { println("invokeWithLambda") }
invokeWithLambda { println("invokeWithLambda") }
FileSource().read(arrayOf('A'.toByte(), 'B'.toByte()))
可變數(shù)量的參數(shù)(Varargs)
函數(shù)的參數(shù)(通常是最后一個(gè))可以用 vararg
修飾符標(biāo)記:
fun varargFun(method: String = "varargFun", vararg s: Int) {
s.forEach { print(it) }
}
val b = intArrayOf(6, 8)
// vararg 參數(shù) 類型是基本類型,即是 *Array 類型 否則 Array<out T>
varargFun("1", 2, 4, *b, 10)
伸展(spread)操作符(在數(shù)組前面加 *
),可以數(shù)組元素添加到vararg 變量中去
返回 Unit 的函數(shù)
如果一個(gè)函數(shù)不返回任何有用的值进陡,它的返回類型是 Unit
愿阐。Unit
是一種只有一個(gè)值——Unit
的類型。這個(gè)值不需要顯式返回四濒。Unit 就像Java 的 Void
fun printHello(name: String?): Unit {
if (name != null)
println("Hello ${name}")
else
println("Hi there!")
// `return Unit` 或者 `return` 是可選的
}
單表達(dá)式函數(shù)
當(dāng)函數(shù)返回單個(gè)表達(dá)式時(shí)换况,可以省略花括號(hào)并且在 = 符號(hào)之后指定代碼體即可。當(dāng)返回值類型可由編譯器推斷時(shí)盗蟆,顯式聲明返回類型是可選的, 但具有塊代碼體的函數(shù)必須始終顯式指定返回類型戈二。
fun double(x: Int) = x * 2
中綴表示法
Kotlin支持?jǐn)?shù)字運(yùn)算的標(biāo)準(zhǔn)集,正是用了中綴表示法,當(dāng)函數(shù)滿足以下條件就能用 infix
關(guān)鍵字標(biāo)注
- 他們是成員函數(shù)或擴(kuò)展函數(shù)
- 他們只有一個(gè)參數(shù)
infix fun String.append(s: String): String {
return "$this$s"
}
infix fun call(method: String) {
println("call#method= $method")
}
val s = "infix" append " gc"
println(s)
this call ("append")
函數(shù)作用域
在 Kotlin 中函數(shù)可以在文件頂層聲明喳资,這意味著你不需要像一些語言如 Java觉吭、C# 或 Scala 那樣創(chuàng)建一個(gè)類來保存一個(gè)函數(shù)。此外除了頂層函數(shù)仆邓,Kotlin 中函數(shù)也可以聲明在局部作用域鲜滩、作為成員函數(shù)以及擴(kuò)展函數(shù)。
- 在類或?qū)ο髢?nèi)部定義的函數(shù)——成員函數(shù)
- 一個(gè)函數(shù)在另一個(gè)函數(shù)內(nèi)部——局部函數(shù)
//成員函數(shù)
fun memberFun() {
val visited = ""
fun partialFun() { //局部函數(shù)
println(visited)
}
partialFun()
}
泛型函數(shù)
函數(shù)可以有泛型參數(shù)节值,通過在函數(shù)名前使用尖括號(hào)指定徙硅。
fun <T> singletonList(item: T): List<T> {
return listOf(item)
}
高階函數(shù)
高階函數(shù)是將函數(shù)用作參數(shù)或返回值的函數(shù)。
//函數(shù)用作參數(shù) () -> Unit 不帶參數(shù)并 且返回 Unit 類型值的函數(shù)
fun post(runnable: () -> Unit) {
println("post before")
runnable()
println("post after")
}
fun postDelay(delay: Int, runnable: () -> Unit) {
println("postDelay before")
runnable()
println("postDelay after")
}
fun test() {
post(this::sayHi) //函數(shù)引用
post { println("post") }
postDelay(1000) { println("postDelay") }
}
() -> Unit
被稱為函數(shù)類型 , ::
操作符可參見函數(shù)引用, 當(dāng)一個(gè)函數(shù)接受另一個(gè)函數(shù)作為最后一個(gè)參數(shù)搞疗,lambda 表達(dá)式參數(shù)可以在圓括號(hào)參數(shù)列表之外傳遞嗓蘑。 參見 callSuffix 的語法。
Lambda 表達(dá)式與匿名函數(shù)
一個(gè) lambda 表達(dá)式或匿名函數(shù)是一個(gè)“函數(shù)字面值”匿乃,即一個(gè)未聲明的函數(shù)桩皿, 作為表達(dá)式傳遞。
Lambda 表達(dá)式語法
lambda 表達(dá)式總是被大括號(hào)括著,完整語法形式的參數(shù)聲明放在括號(hào)內(nèi)幢炸,并有可選的類型標(biāo)注泄隔, 函數(shù)體跟在一個(gè) ->
符號(hào)之后。
println({}) //輸出: () -> kotlin.Unit
println({ "String" })//輸出: () -> kotlin.String
val string = { "String" }
println(string())//輸出: String
挖槽,上面的是什么鬼宛徊。沒了解Lambda 表達(dá)式 的佛嬉,當(dāng)然會(huì)困惑不已。
fun explicitAnonymous(): () -> Int {
return { -> 1 } //沒參數(shù)不能有括號(hào)() -> 也可略
}
這樣一來就簡單明了闸天。{}
聲明了個(gè)匿名函數(shù),編譯器作以下處理
local final fun <anonymous>(): Unit
當(dāng)一個(gè)空參數(shù)的匿名函數(shù), 如 { "String" }
,編譯器會(huì)將lambda 主體中的最后一個(gè)或可能是單個(gè))表達(dá)式會(huì)視為返回值巷燥。若是{ "String";1 }
則輸出 () -> kotlin.Int
可選的類型標(biāo)注,單表達(dá)式函數(shù)時(shí),顯式聲明返回類型是可選的,匿名的參數(shù)類型也是可選的。非單表達(dá)式函數(shù)時(shí),則變量名可選号枕。
val sum = { x: Int, y: Int -> x + y } //val sum: (Int, Int) → Int
val sum2: (Int, Int) -> Int = { x, y -> x + y } //val sum2: (Int, Int) → Int
fun sum3(sum: (Int, Int) -> Int) {
println(sum(0,0))
}
fun sum4(sum: (a: Int, b: Int) -> Int) {
println(sum)
}
sum3 { a, b -> 1 + 3 }
println(sum(1, 2))
在 Kotlin 中Lambda表達(dá)式約定
- 函數(shù)的最后一個(gè)參數(shù)是一個(gè)函數(shù),并且你傳遞一個(gè) lambda 表達(dá)式作為相應(yīng)的參數(shù)陨享,你可以在圓括號(hào)之外傳遞
- lambda 是該調(diào)用的唯一參數(shù)葱淳,則調(diào)用中的圓括號(hào)可以完全省略钝腺。
- 函數(shù)字面值只有一個(gè)參數(shù)時(shí), 那么它的聲明可以省略(連同
->
)赞厕,其名稱是it
艳狐。 - 未使用的變量可用下劃線取代其名稱
- lambda 隱式返回最后一個(gè)表達(dá)式的值,可以用限定的返回語法顯式返回
fun <T> filter(predicate: (T) -> Boolean) {
TODO()
}
filter<Int>() { it > 0 } //() 可略
filter<Int> { it > 0 }
filter<Int> { _ -> false }
filter<Int> {
val shouldFilter = it > 0
return@filter shouldFilter
}
匿名函數(shù)
顧名思義,與常規(guī)函數(shù)相同不需要指定函數(shù)名
val sumAnonymous = fun(x: Int, y: Int) = x + y //返回類型可以自動(dòng)推斷
println(sumAnonymous(1, 3))
val sumAnonymous2 = fun(x: Int, y: Int): Int {
return x + y
}
filter<Int>(fun(item) = item > 0) //推斷出的參數(shù)類型可以省略. 只能在括號(hào)內(nèi)傳遞
匿名函數(shù)和lambda 是有區(qū)別的,匿名函數(shù)參數(shù)只能在括號(hào)內(nèi)傳遞皿桑。 允許將函數(shù)留在圓括號(hào)外的簡寫語法僅適用于 lambda 表達(dá)式毫目。Lambda表達(dá)式與匿名函數(shù)之間的另一個(gè)區(qū)別是非局部返回的行為。一個(gè)不帶標(biāo)簽的 return 語句總是在用 fun 關(guān)鍵字聲明的函數(shù)中返回诲侮。這意味著 lambda 表達(dá)式中的 return 將從包含它的函數(shù)返回镀虐,而匿名函數(shù)中的 return將從匿名函數(shù)自身返回。
閉包
Lambda 表達(dá)式或者匿名函數(shù)(以及局部函數(shù)和對(duì)象表達(dá)式) 可以訪問其 閉包 沟绪,即在外部作用域中聲明的變量刮便。 與 Java 不同的是可以修改閉包中捕獲的變量:
var aNumber = 0
run {
aNumber += 1
}
val add = fun() {
aNumber += 1
}
add()
println("aNumber: $aNumber")
帶接收者的函數(shù)字面值
Kotlin 提供了使用指定的 接收者對(duì)象 調(diào)用函數(shù)字面值的功能。 在函數(shù)字面值的函數(shù)體中绽慈,可以調(diào)用該接收者對(duì)象上的方法而無需任何額外的限定符恨旱。 這類似于擴(kuò)展函數(shù),它允許你在函數(shù)體內(nèi)訪問接收者對(duì)象的成員坝疼。 其用法的最重要的示例之一是類型安全的 Groovy-風(fēng)格構(gòu)建器搜贤。
val sumR = fun Int.(other: Int): Int = this + other //val sumR: Int.(Int) → Int
println(1.sumR(2))
內(nèi)聯(lián)函數(shù)
要知道使用高階函數(shù)時(shí),每一個(gè)函數(shù)都是一個(gè)對(duì)象,且會(huì)捕獲一個(gè)閉包钝凶。 所以帶來一些運(yùn)行時(shí)的效率損失,即那些在函數(shù)體內(nèi)會(huì)訪問到的變量仪芒。 內(nèi)存分配(對(duì)于函數(shù)對(duì)象和類)和虛擬調(diào)用會(huì)引入運(yùn)行時(shí)間開銷。
kotlin 支持 inline
修飾具有l(wèi)ambda參數(shù)的函數(shù),以消除這類的開銷腿椎。(僅支持頂層桌硫、成員函數(shù),即不支持局函數(shù))
inline fun <T> lockInline(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body()
} finally {
lock.unlock()
}
}
內(nèi)聯(lián)原理其實(shí)是編譯器拷貝代碼副本(如:body () -> T
),這可能導(dǎo)致生成的代碼增加啃炸,但在循環(huán)中的“超多態(tài)(megamorphic)” 情況下,將在性能上有所提升取试。
不具有l(wèi)ambda參數(shù)的函數(shù):
inline fun test() { //warn 內(nèi)聯(lián)函數(shù)最適用于具有l(wèi)ambda參數(shù)的函數(shù)
NOTE:內(nèi)聯(lián)函數(shù)不支持局部函數(shù)
禁用內(nèi)聯(lián)
對(duì)于具有多個(gè)lambda參數(shù)的內(nèi)聯(lián)函數(shù)來說闺骚,默認(rèn)內(nèi)聯(lián), 可用 noinline
修飾lambda參數(shù),禁用內(nèi)聯(lián)。
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
// ……
}
noinline
僅在內(nèi)聯(lián)函數(shù)中可使用
inline fun foo(noinline notInlined: () -> Unit) {
// …… 僅一個(gè)參數(shù)又用 noinline 修飾, inline 將無效
}
非局部返回
lambda 表達(dá)式內(nèi)部不允許無標(biāo)簽的return
, 但傳給的函數(shù)是內(nèi)聯(lián)的,該 return 也可以內(nèi)聯(lián)芒率,所以它是允許返回。稱為非局部返回驳癌。
fun <T> lock(body: () -> T): Unit {
}
inline fun <T> lockInline(body: () -> T): Unit {
}
lock {
return // 不允許不帶標(biāo)簽的return. return@lock
}
lockInline{
return
}
循環(huán)中常用這種結(jié)構(gòu)
fun hasZeros(ints: List<Int>): Boolean {
ints.forEach {
if (it == 0) return true // 從 hasZeros 返回
}
return false
}
一些內(nèi)聯(lián)函數(shù)可能調(diào)用傳給它們的不是直接來自函數(shù)體娄琉、而是來自另一個(gè)執(zhí)行上下文的 lambda 表達(dá)式參數(shù),例如來自局部對(duì)象或嵌套函數(shù)筑公。在這種情況下雳窟,該 lambda 表達(dá)式中也不允許非局部控制流。為了標(biāo)識(shí)這種情況匣屡,該 lambda 表達(dá)式參數(shù)需要用 crossinline
修飾符標(biāo)記:
inline fun post(crossinline body: () -> Unit) {
Runnable { body() }
}
具體化的類型參數(shù)
內(nèi)聯(lián)函數(shù)能有具體化的類型參數(shù)(Reified type parameters),用 reified
修飾符來限定類型參數(shù)
在前面泛型函數(shù)學(xué)習(xí)中,是不能有具體化參數(shù),獲取 class時(shí)帶來不便封救。
fun <T> findType(t: T) {
//因?yàn)門 不是靜態(tài)已知的 Kotlin 類的引用拇涤,所以不能 T::class
println((t as Any)::class)
}
//內(nèi)聯(lián)函數(shù)支持具體化的類型參數(shù),不需要反射,正常的操作符如 !is 和 as 能正常使用
inline fun <reified T : Number> findReifiedType(t: T) {
println(T::class)
println(Int.MIN_VALUE is T)
}
內(nèi)聯(lián)屬性
inline
修飾符還可以修飾沒有幕后字段的屬性的訪問器(有setter/getter),可單獨(dú)標(biāo)注誉结。
val max inline get() = Int.MAX_VALUE
inline val max1 get() = Int.MAX_VALUE
inline val max2 inline get() = Int.MAX_VALUE //編譯也ok 鹅士。。惩坑。
//Inline property cannot have backing field
var count = 0
var counter
inline get() = count //set/get 其中一個(gè)標(biāo)注為inline, 都不能使用 backing field
inline set(value) {
count = value
}
//
inline var doubleCounter
get() = count * 2 //set/get 其中一個(gè)標(biāo)注為inline, 都不能使用 backing field
set(value) {
count *= value
}
公有 API 內(nèi)聯(lián)函數(shù)的限制
當(dāng)一個(gè)內(nèi)聯(lián)函數(shù)是 public
或 protected
而不是 private
或 internal
聲明的一部分時(shí)掉盅,就會(huì)認(rèn)為它是一個(gè)模塊級(jí)的公有 API∫允妫可以在其他模塊中調(diào)用它趾痘,并且也可以在調(diào)用處內(nèi)聯(lián)這樣的調(diào)用。
這帶來了一些由模塊做這樣變更時(shí)導(dǎo)致的二進(jìn)制兼容的風(fēng)險(xiǎn)——聲明一個(gè)內(nèi)聯(lián)函數(shù)但調(diào)用它的模塊在它修改后并沒有重新編譯稀轨。
為了消除這種由非公有 API 變更引入的不兼容的風(fēng)險(xiǎn)扼脐,公有 API 內(nèi)聯(lián)函數(shù)體內(nèi)不允許使用非公有聲明,即奋刽,不允許使用 private
與 internal
聲明以及其部件瓦侮。
一個(gè) internal
聲明可以由 @PublishedApi
標(biāo)注,這會(huì)允許它在公有 API 內(nèi)聯(lián)函數(shù)中使用佣谐。當(dāng)一個(gè) internal
內(nèi)聯(lián)函數(shù)標(biāo)記有 @PublishedApi
時(shí)肚吏,也會(huì)像公有函數(shù)一樣檢查其函數(shù)體。
//公有 API 內(nèi)聯(lián)函數(shù)限制使用private 與 internal 聲明以及其部件 (頂層聲明)
inline fun publishApi(body: () -> Unit) {
privateFun()
internalFun()
}
@PublishedApi //檢查其函數(shù)體加以限制
internal inline fun internalApi(body: () -> Unit) {
privateFun()
internalFun()
}
private fun privateFun(): Unit {
}
internal fun internalFun(): Unit {
}
Kotlin與 Java 混合開發(fā)
Kotlin 中調(diào)用 Java
已映射類型
在Kotlin 中使用Java 代碼狭魂,編譯期間罚攀, Java 的原生類型映射到相應(yīng)的 Kotlin 類型,運(yùn)行時(shí)表示保持不變雌澄。
Kotlin 類型 | Java 類型 |
---|---|
kotlin.Byte | byte |
kotlin.Short | short |
kotlin.Int | int |
kotlin.Long | long |
kotlin.Char | char |
kotlin.Float | float |
kotlin.Double | double |
kotlin.Boolean | boolean |
Java 的裝箱原始類型映射到可空的 Kotlin 類型:
Kotlin 類型 | Java 類型 |
---|---|
kotlin.Byte? | java.lang.Byte |
kotlin.Short斋泄? | java.lang. Short |
kotlin.Int? | java.lang.Integer |
kotlin.Long? | java.lang.Long |
kotlin.Char? | java.lang.Character |
kotlin.Float? | java.lang.Float |
kotlin.Double? | java.lang.Double |
kotlin.Boolean? | java.lang. Boolean |
一些非原生的內(nèi)置類型也會(huì)作映射:
Kotlin 類型 | Java 類型 |
---|---|
kotlin.Any! | java.lang.Object |
kotlin.Cloneable! | java.lang.Cloneable |
kotlin.Comparable! | java.lang.Comparable |
kotlin.Enum! | java.lang.Enum |
kotlin.Annotation! | java.lang.Annotation |
kotlin.Deprecated! | java.lang.Deprecated |
kotlin.CharSequence! | java.lang.CharSequence |
kotlin.String! | java.lang.String |
kotlin.Number! | java.lang.Number |
kotlin.Throwable! | java.lang.Throwable |
NOTE: String!
為平臺(tái)類型表示法
集合類型在 Kotlin 中可以是只讀的或可變的,因此 Java 集合類型作如下映射: (下表中的所有 Kotlin 類型都駐留在 kotlin.collections
包中):
Java 類型 | Kotlin 只讀類型 | Kotlin 可變類型 | 加載的平臺(tái)類型 |
---|---|---|---|
Iterator<T> | Iterator<T> | MutableIterator<T> | (Mutable)Iterator<T>! |
Iterable<T> | Iterable<T> | MutableIterable<T> | (Mutable)Iterable<T>! |
Collection<T> | Collection<T> | MutableCollection<T> | (Mutable)Collection<T>! |
Set<T> | Set<T> | MutableSet<T> | (Mutable)Set<T>! |
List<T> | List<T> | MutableList<T> | (Mutable)List<T>! |
ListIterator<T> | ListIterator<T> | MutableListIterator<T> | (Mutable)ListIterator<T>! |
Map<K, V> | Map<K, V> | MutableMap<K, V> | (Mutable)Map<K, V>! |
Map.Entry<K, V> | Map.Entry<K, V> | MutableMap.MutableEntry<K,V> | (Mutable)Map.(Mutable)Entry<K, V>! |
請注意镐牺,用作類型參數(shù)的裝箱原始類型映射到平臺(tái)類型: 例如炫掐,List<java.lang.Integer>
在 Kotlin 中會(huì)成為 List<Int!>
。
Java 的數(shù)組按如下所述映射:
Java 類型 | Kotlin 類型 |
---|---|
int[] | kotlin.IntArray! |
String[] | kotlin.Array<(out) String>! |
空安全和平臺(tái)類型
Java 中任何引用都可能是 null
睬涧,而Kotlin 類型安全(空安全)募胃。 Java 聲明的類型在 Kotlin 中空檢查跟Java相同(可空,非空)稱為平臺(tái)類型畦浓。平臺(tái)類型可用助記符痹束!
加在后面來表示,但切記不能在程序中這樣寫,kotlin 并沒有相應(yīng)語法,IDE Doc 可以顯示讶请。
val nullAny = JavaDataType.nullObj //實(shí)際: val nullAny: Any!
val safeNullAny: Any? = JavaDataType.nullObj
println(safeNullAny?.hashCode())
println(nullAny?.hashCode()) //null check
val notNullAny: Any = JavaDataType.nullObj //賦值時(shí) NPE
nullAny.hashCode() //使用時(shí) NPE
NOTE:只要不是Java基本類型,在Kotlin中都會(huì)映射為 T!
Getter 和 Setter
遵循 Java 約定的 getter 和 setter 的方法(名稱以 get
開頭的無參數(shù)方法和以 set
開頭的單參數(shù)方法)在 Kotlin 中表示為屬性祷嘶。 Boolean
訪問器方法(其中 getter 的名稱以 is
開頭而 setter 的名稱以 set
開頭)會(huì)表示為與 getter 方法具有相同名稱的屬性。 例如:
import java.util.Calendar
fun calendarDemo() {
val calendar = Calendar.getInstance()
if (calendar.firstDayOfWeek == Calendar.SUNDAY) { // 調(diào)用 getFirstDayOfWeek()
calendar.firstDayOfWeek = Calendar.MONDAY // 調(diào)用ll setFirstDayOfWeek()
}
if (!calendar.isLenient) { // 調(diào)用 isLenient()
calendar.isLenient = true // 調(diào)用 setLenient()
}
}
請注意,如果 Java 類只有一個(gè) setter抹蚀,它在 Kotlin 中不會(huì)作為屬性可見剿牺,因?yàn)?Kotlin 目前不支持只寫(set-only)屬性。
返回 void 的方法
如果一個(gè) Java 方法返回 void环壤,那么從 Kotlin 調(diào)用時(shí)中返回 Unit
。 萬一有人使用其返回值钞诡,它將由 Kotlin 編譯器在調(diào)用處賦值郑现, 因?yàn)樵撝当旧硎穷A(yù)先知道的(是 Unit
)。
將 Kotlin 中是關(guān)鍵字的 Java 標(biāo)識(shí)符進(jìn)行轉(zhuǎn)義
一些 Kotlin 關(guān)鍵字在 Java 中是有效標(biāo)識(shí)符:in荧降、 object接箫、 is 等等。 如果一個(gè) Java 庫使用了 Kotlin 關(guān)鍵字作為方法朵诫,你仍然可以通過反引號(hào)(`)字符轉(zhuǎn)義它來調(diào)用該方法
foo.`is`(bar)
Java 泛型
Kotlin 的泛型與 Java 有點(diǎn)不同(參見泛型)辛友。當(dāng)將 Java 類型導(dǎo)入 Kotlin 時(shí),我們會(huì)執(zhí)行一些轉(zhuǎn)換:
- Java 的通配符轉(zhuǎn)換成類型投影
-
Foo<? extends Bar>
轉(zhuǎn)換成Foo<out Bar!>!
-
Foo<? super Bar>
轉(zhuǎn)換成Foo<in Bar!>!
-
- Java的原始類型轉(zhuǎn)換成星投影
-
List
轉(zhuǎn)換成List<*>!
剪返,即List<out Any?>!
-
和 Java 一樣废累,Kotlin 在運(yùn)行時(shí)不保留泛型,即對(duì)象不攜帶傳遞到他們構(gòu)造器中的那些類型參數(shù)的實(shí)際類型脱盲。 即 ArrayList<Integer>()
和 ArrayList<Character>()
是不能區(qū)分的邑滨。 這使得執(zhí)行 is-檢測不可能照顧到泛型。 Kotlin 只允許 is-檢測星投影的泛型類型:
if (a is List<Int>) // 錯(cuò)誤:無法檢查它是否真的是一個(gè) Int 列表
// but
if (a is List<*>) // OK:不保證列表的內(nèi)容
Java 集合
java 集合類型映射的平臺(tái)類型都是可變的钱反,用法如kotlin 一樣掖看,而且 操作符約定同樣有效
Java 數(shù)組
與 Java 不同,Kotlin 中的數(shù)組是不型變的面哥。這意味著 Kotlin 不允許我們把一個(gè) Array<String>
賦值給一個(gè) Array<Any>
哎壳, 從而避免了可能的運(yùn)行時(shí)故障。Kotlin 也禁止我們把一個(gè)子類的數(shù)組當(dāng)做超類的數(shù)組傳遞給 Kotlin 的方法尚卫, 但是對(duì)于 Java 方法归榕,這是允許的(通過 Array<(out) String>!
這種形式的平臺(tái)類型)。
Java 平臺(tái)上焕毫,數(shù)組會(huì)使用原生數(shù)據(jù)類型以避免裝箱/拆箱操作的開銷蹲坷。 由于 Kotlin 隱藏了這些實(shí)現(xiàn)細(xì)節(jié),因此需要一個(gè)變通方法來與 Java 代碼進(jìn)行交互邑飒。 對(duì)于每種原生類型的數(shù)組都有一個(gè)特化的類(IntArray
循签、 DoubleArray
、 CharArray
等等)來處理這種情況疙咸。 它們與 Array
類無關(guān)县匠,并且會(huì)編譯成 Java 原生類型數(shù)組以獲得最佳性能。
假設(shè)有一個(gè)接受 int 數(shù)組索引的 Java 方法:
public class JavaArrayExample {
public void removeIndices(int[] indices) {
// 在此編碼……
}
}
在 Kotlin 中你可以這樣傳遞一個(gè)原生類型的數(shù)組:
val javaObj = JavaArrayExample()
val array = intArrayOf(0, 1, 2, 3)
javaObj.removeIndices(array) // 將 int[] 傳給方法
當(dāng)編譯為 JVM 字節(jié)代碼時(shí),編譯器會(huì)優(yōu)化對(duì)數(shù)組的訪問乞旦,這樣就不會(huì)引入任何開銷:
val array = arrayOf(1, 2, 3, 4)
array[x] = array[x] * 2 // 不會(huì)實(shí)際生成對(duì) get() 和 set() 的調(diào)用
for (x in array) { // 不會(huì)創(chuàng)建迭代器
print(x)
}
即使當(dāng)我們使用索引定位時(shí)贼穆,也不會(huì)引入任何開銷
for (i in array.indices) {// 不會(huì)創(chuàng)建迭代器
array[i] += 2
}
最后,in-檢測也沒有額外開銷
if (i in array.indices) { // 同 (i >= 0 && i < array.size)
print(array[i])
}
Java 可變參數(shù)
Java 類有時(shí)聲明一個(gè)具有可變數(shù)量參數(shù)(varargs)的方法來使用索引兰粉。
public class JavaArrayExample {
public void removeIndicesVarArg(int... indices) {
// 在此編碼……
}
}
在這種情況下故痊,你需要使用展開運(yùn)算符 *
來傳遞 IntArray
:
val javaObj = JavaArrayExample()
val array = intArrayOf(0, 1, 2, 3)
javaObj.removeIndicesVarArg(*array)
目前無法傳遞 null 給一個(gè)聲明為可變參數(shù)的方法。
操作符
由于 Java 無法標(biāo)記用于運(yùn)算符語法的方法玖姑,Kotlin 允許具有正確名稱和簽名的任何 Java 方法作為運(yùn)算符重載和其他約定(invoke()
等)使用愕秫。 不允許使用中綴調(diào)用語法調(diào)用 Java 方法。
受檢異常
在 Kotlin 中焰络,所有異常都是非受檢的戴甩,這意味著編譯器不會(huì)強(qiáng)迫你捕獲其中的任何一個(gè)。 因此闪彼,當(dāng)你調(diào)用一個(gè)聲明受檢異常的 Java 方法時(shí)甜孤,Kotlin 不會(huì)強(qiáng)迫你做任何事情:
對(duì)象方法
當(dāng) Java 類型導(dǎo)入到 Kotlin 中時(shí),類型 java.lang.Object
的所有引用都成了 Any
畏腕。 而因?yàn)?Any
不是平臺(tái)指定的缴川,它只聲明了 toString()
、hashCode()
和 equals()
作為其成員郊尝, 所以為了能用到 java.lang.Object
的其他成員二跋,Kotlin 要用到擴(kuò)展函數(shù)。
wait()/notify()
Effective Java 第 69 條善意地建議優(yōu)先使用并發(fā)工具(concurrency utilities)而不是 wait()
和 notify()
流昏。 因此扎即,類型 Any
的引用不提供這兩個(gè)方法。 如果你真的需要調(diào)用它們的話况凉,你可以將其轉(zhuǎn)換為 java.lang.Object
:
(foo as java.lang.Object).wait()
getClass()
要取得對(duì)象的 Java 類谚鄙,請?jiān)?a target="_blank" rel="nofollow">類引用上使用 java
擴(kuò)展屬性。
val fooClass = foo::class.java
上面的代碼使用了自 Kotlin 1.1 起支持的綁定的類引用刁绒。你也可以使用 javaClass
擴(kuò)展屬性闷营。
val fooClass = foo.javaClass
clone()
要覆蓋 clone()
,需要繼承 kotlin.Cloneable
:
class Example : Cloneable {
override fun clone(): Any { …… }
}
不要忘記 Effective Java 的第 11 條: 謹(jǐn)慎地改寫clone知市。
finalize()
要覆蓋 finalize()
话侧,所有你需要做的就是簡單地聲明它贷祈,而不需要 override 關(guān)鍵字:
class C {
protected fun finalize() {
// 終止化邏輯
}
}
根據(jù) Java 的規(guī)則俩功,finalize()
不能是 private 的憾儒。
訪問靜態(tài)成員
Java 類的靜態(tài)成員會(huì)形成該類的“伴生對(duì)象”。我們無法將這樣的“伴生對(duì)象”作為值來傳遞跟啤, 但可以顯式訪問其成員诽表,例如:
val character = Character
if (Character.isLetter('A')) {
// ……
}
Java 反射
Java 反射適用于 Kotlin 類唉锌,反之亦然。如上所述竿奏,你可以使用 instance::class.java
,ClassName::class.java
或者 instance.javaClass
通過 java.lang.Class
來進(jìn)入 Java 反射袄简。
其他支持的情況包括為一個(gè) Kotlin 屬性獲取一個(gè) Java 的 getter/setter 方法或者幕后字段、為一個(gè) Java 字段獲取一個(gè) KProperty
泛啸、為一個(gè) KFunction
獲取一個(gè) Java 方法或者構(gòu)造函數(shù)绿语,反之亦然。
SAM 轉(zhuǎn)換
就像 Java 8 一樣候址,Kotlin 支持 SAM 轉(zhuǎn)換汞舱。這意味著 Kotlin 函數(shù)字面值可以被自動(dòng)的轉(zhuǎn)換成只有一個(gè)非默認(rèn)方法的 Java 接口的實(shí)現(xiàn),只要這個(gè)方法的參數(shù)類型能夠與這個(gè) Kotlin 函數(shù)的參數(shù)類型相匹配宗雇。
你可以這樣創(chuàng)建 SAM 接口的實(shí)例:
val runnable = Runnable { println("This runs in a runnable") }
……以及在方法調(diào)用中:
val executor = ThreadPoolExecutor()
// Java 簽名:void execute(Runnable command)
executor.execute { println("This runs in a thread pool") }
如果 Java 類有多個(gè)接受函數(shù)式接口的方法,那么可以通過使用將 lambda 表達(dá)式轉(zhuǎn)換為特定的 SAM 類型的適配器函數(shù)來選擇需要調(diào)用的方法莹规。這些適配器函數(shù)也會(huì)按需由編譯器生成赔蒲。
executor.execute(Runnable { println("This runs in a thread pool") })
請注意,SAM 轉(zhuǎn)換只適用于接口良漱,而不適用于抽象類舞虱,即使這些抽象類也只有一個(gè)抽象方法。
還要注意母市,此功能只適用于 Java 互操作矾兜;因?yàn)?Kotlin 具有合適的函數(shù)類型,所以不需要將函數(shù)自動(dòng)轉(zhuǎn)換為 Kotlin 接口的實(shí)現(xiàn)患久,因此不受支持椅寺。
Java中調(diào)用 Kotlin
Java 可以輕松調(diào)用 Kotlin 代碼。
屬性
Kotlin 屬性會(huì)編譯成以下 Java 元素:
- 一個(gè) getter 方法蒋失,名稱通過加前綴
get
算出返帕; - 一個(gè) setter 方法,名稱通過加前綴
set
算出(只適用于var
屬性)篙挽; - 一個(gè)私有字段荆萤,與屬性名稱相同(僅適用于具有幕后字段的屬性)。
例如铣卡,var firstName: String
編譯成以下 Java 聲明:
private String firstName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
如果屬性的名稱以 is
開頭链韭,則使用不同的名稱映射規(guī)則:getter 的名稱與屬性名稱相同,并且 setter 的名稱是通過將 is
替換為 set
獲得煮落。 例如敞峭,對(duì)于屬性 isOpen
,其 getter 會(huì)稱做 isOpen()
州邢,而其 setter 會(huì)稱做 setOpen()
儡陨。 這一規(guī)則適用于任何類型的屬性褪子,并不僅限于 Boolean
骗村。
包級(jí)函數(shù)
在 org.foo.bar
包內(nèi)的 example.kt
文件中聲明的所有的函數(shù)和屬性嫌褪,包括擴(kuò)展函數(shù), 都編譯成一個(gè)名為 org.foo.bar.ExampleKt
的 Java 類的靜態(tài)方法胚股。
// example.kt
package demo
class Foo
fun bar() {
}
// Java
new demo.Foo();
demo.ExampleKt.bar();
可以使用 @JvmName
注解修改生成的 Java 類的類名:
@file:JvmName("DemoUtils")
package demo
class Foo
fun bar() {
}
// Java
new demo.Foo();
demo.DemoUtils.bar();
如果多個(gè)文件中生成了相同的 Java 類名(包名相同并且類名相同或者有相同的 @JvmName
注解)通常是錯(cuò)誤的笼痛。然而,編譯器能夠生成一個(gè)單一的 Java 外觀類琅拌,它具有指定的名稱且包含來自所有文件中具有該名稱的所有聲明缨伊。 要啟用生成這樣的外觀,請?jiān)谒邢嚓P(guān)文件中使用 @JvmMultifileClass 注解进宝。
// oldutils.kt
@file:JvmName("Utils")
@file:JvmMultifileClass
package demo
fun foo() {
}
// newutils.kt
@file:JvmName("Utils")
@file:JvmMultifileClass
package demo
fun bar() {
}
// Java
demo.Utils.foo();
demo.Utils.bar();
實(shí)例字段
如果需要在 Java 中將 Kotlin 屬性作為字段暴露刻坊,那就需要使用 @JvmField
注解對(duì)其標(biāo)注。 該字段將具有與底層屬性相同的可見性党晋。如果一個(gè)屬性有幕后字段(backing field)谭胚、非私有、沒有 open
/override
或者 const
修飾符并且不是被委托的屬性未玻,那么你可以用 @JvmField
注解該屬性灾而。
class C(id: String) {
@JvmField val ID = id
}
// Java
class JavaClient {
public String getID(C c) {
return c.ID;
}
}
延遲初始化的屬性(在Java中)也會(huì)暴露為字段。 該字段的可見性與 lateinit
屬性的 setter 相同扳剿。
靜態(tài)字段
在命名對(duì)象或伴生對(duì)象中聲明的 Kotlin 屬性會(huì)在該命名對(duì)象或包含伴生對(duì)象的類中具有靜態(tài)幕后字段旁趟。
通常這些字段是私有的,但可以通過以下方式之一暴露出來:
-
@JvmField
注解庇绽; -
lateinit
修飾符锡搜; -
const
修飾符。
使用 @JvmField
標(biāo)注這樣的屬性使其成為與屬性本身具有相同可見性的靜態(tài)字段敛劝。
class Key(val value: Int) {
companion object {
@JvmField
val COMPARATOR: Comparator<Key> = compareBy<Key> { it.value }
}
}
// Java
Key.COMPARATOR.compare(key1, key2);
// Key 類中的 public static final 字段
在命名對(duì)象或者伴生對(duì)象中的一個(gè)延遲初始化的屬性具有與屬性 setter 相同可見性的靜態(tài)幕后字段余爆。
object Singleton {
lateinit var provider: Provider
}
// Java
Singleton.provider = new Provider();
// 在 Singleton 類中的 public static 非-final 字段
用 const
標(biāo)注的(在類中以及在頂層的)屬性在 Java 中會(huì)成為靜態(tài)字段:
// 文件 example.kt
object Obj {
const val CONST = 1
}
class C {
companion object {
const val VERSION = 9
}
}
const val MAX = 239
在 Java 中:
int c = Obj.CONST;
int d = ExampleKt.MAX;
int v = C.VERSION;
靜態(tài)方法
如上所述,Kotlin 將包級(jí)函數(shù)表示為靜態(tài)方法夸盟。 Kotlin 還可以為命名對(duì)象或伴生對(duì)象中定義的函數(shù)生成靜態(tài)方法蛾方,如果你將這些函數(shù)標(biāo)注為 @JvmStatic
的話。 如果你使用該注解上陕,編譯器既會(huì)在相應(yīng)對(duì)象的類中生成靜態(tài)方法桩砰,也會(huì)在對(duì)象自身中生成實(shí)例方法。 例如:
class C {
companion object {
@JvmStatic fun foo() {}
fun bar() {}
}
}
現(xiàn)在释簿,foo()
在 Java 中是靜態(tài)的亚隅,而 bar()
不是:
C.foo(); // 沒問題
C.bar(); // 錯(cuò)誤:不是一個(gè)靜態(tài)方法
C.Companion.foo(); // 保留實(shí)例方法
C.Companion.bar(); // 唯一的工作方式
對(duì)于命名對(duì)象也同樣:
object Obj {
@JvmStatic fun foo() {}
fun bar() {}
}
在 Java 中:
Obj.foo(); // 沒問題
Obj.bar(); // 錯(cuò)誤
Obj.INSTANCE.bar(); // 沒問題,通過單例實(shí)例調(diào)用
Obj.INSTANCE.foo(); // 也沒問題
@JvmStatic
注解也可以應(yīng)用于對(duì)象或伴生對(duì)象的屬性庶溶, 使其 getter 和 setter 方法在該對(duì)象或包含該伴生對(duì)象的類中是靜態(tài)成員煮纵。
可見性
Kotlin 的可見性以下列方式映射到 Java:
-
private
成員編譯成private
成員懂鸵; -
private
的頂層聲明編譯成包級(jí)局部聲明; -
protected
保持protected
(注意 Java 允許訪問同一個(gè)包中其他類的受保護(hù)成員行疏, 而 Kotlin 不能匆光,所以 Java 類會(huì)訪問更廣泛的代碼); -
internal
聲明會(huì)成為 Java 中的public
酿联。internal
類的成員會(huì)通過名字修飾终息,使其更難以在 Java 中意外使用到,并且根據(jù) Kotlin 規(guī)則使其允許重載相同簽名的成員而互不可見贞让; -
public
保持public
周崭。
KClass
有時(shí)你需要調(diào)用有 KClass
類型參數(shù)的 Kotlin 方法。 因?yàn)闆]有從 Class
到 KClass
的自動(dòng)轉(zhuǎn)換喳张,所以你必須通過調(diào)用 Class<T>.kotlin
擴(kuò)展屬性的等價(jià)形式來手動(dòng)進(jìn)行轉(zhuǎn)換:
kotlin.jvm.JvmClassMappingKt.getKotlinClass(MainView.class)
用 @JvmName 解決簽名沖突
有時(shí)我們想讓一個(gè) Kotlin 中的命名函數(shù)在字節(jié)碼中有另外一個(gè) JVM 名稱续镇。 最突出的例子是由于類型擦除引發(fā)的:
fun List<String>.filterValid(): List<String>
fun List<Int>.filterValid(): List<Int>
這兩個(gè)函數(shù)不能同時(shí)定義,因?yàn)樗鼈兊?JVM 簽名是一樣的:filterValid(Ljava/util/List;)Ljava/util/List;
销部。 如果我們真的希望它們在 Kotlin 中用相同名稱磨取,我們需要用 @JvmName
去標(biāo)注其中的一個(gè)(或兩個(gè)),并指定不同的名稱作為參數(shù):
fun List<String>.filterValid(): List<String>
@JvmName("filterValidInt")
fun List<Int>.filterValid(): List<Int>
在 Kotlin 中它們可以用相同的名稱 filterValid
來訪問柴墩,而在 Java 中,它們分別是 filterValid
和 filterValidInt
凫岖。
同樣的技巧也適用于屬性 x
和函數(shù) getX()
共存:
val x: Int
@JvmName("getX_prop")
get() = 15
fun getX() = 10
生成重載
通常江咳,如果你寫一個(gè)有默認(rèn)參數(shù)值的 Kotlin 函數(shù),在 Java 中只會(huì)有一個(gè)所有參數(shù)都存在的完整參數(shù)簽名的方法可見哥放,如果希望向 Java 調(diào)用者暴露多個(gè)重載歼指,可以使用 @JvmOverloads
注解。
該注解也適用于構(gòu)造函數(shù)甥雕、靜態(tài)方法等踩身。它不能用于抽象方法,包括在接口中定義的方法社露。
class Foo @JvmOverloads constructor(x: Int, y: Double = 0.0) {
@JvmOverloads fun f(a: String, b: Int = 0, c: String = "abc") {
……
}
}
對(duì)于每一個(gè)有默認(rèn)值的參數(shù)挟阻,都會(huì)生成一個(gè)額外的重載,這個(gè)重載會(huì)把這個(gè)參數(shù)和它右邊的所有參數(shù)都移除掉峭弟。在上例中附鸽,會(huì)生成以下代碼 :
// 構(gòu)造函數(shù):
Foo(int x, double y)
Foo(int x)
// 方法
void f(String a, int b, String c) { }
void f(String a, int b) { }
void f(String a) { }
請注意,如次構(gòu)造函數(shù)中所述瞒瘸,如果一個(gè)類的所有構(gòu)造函數(shù)參數(shù)都有默認(rèn)值坷备,那么會(huì)為其生成一個(gè)公有的無參構(gòu)造函數(shù)。這就算沒有 @JvmOverloads
注解也有效情臭。
受檢異常
如上所述省撑,Kotlin 沒有受檢異常赌蔑。 所以,通常 Kotlin 函數(shù)的 Java 簽名不會(huì)聲明拋出異常。 于是如果我們有一個(gè)這樣的 Kotlin 函數(shù):
// example.kt
package demo
fun foo() {
throw IOException()
}
然后我們想要在 Java 中調(diào)用它并捕捉這個(gè)異常:
// Java
try {
demo.Example.foo();
}
catch (IOException e) { // 錯(cuò)誤:foo() 未在 throws 列表中聲明 IOException
// ……
}
因?yàn)?foo()
沒有聲明 IOException
,我們從 Java 編譯器得到了一個(gè)報(bào)錯(cuò)消息拉背。 為了解決這個(gè)問題拘领,要在 Kotlin 中使用 @Throws
注解。
@Throws(IOException::class)
fun foo() {
throw IOException()
}
空安全性
當(dāng)從 Java 中調(diào)用 Kotlin 函數(shù)時(shí)毅访,沒人阻止我們將 null 作為非空參數(shù)傳遞。 這就是為什么 Kotlin 給所有期望非空參數(shù)的公有函數(shù)生成運(yùn)行時(shí)檢測。 這樣我們就能在 Java 代碼里立即得到 NullPointerException
潮孽。
型變的泛型
當(dāng) Kotlin 的類使用了聲明處型變,有兩種選擇可以從 Java 代碼中看到它們的用法筷黔。讓我們假設(shè)我們有以下類和兩個(gè)使用它的函數(shù):
class Box<out T>(val value: T)
interface Base
class Derived : Base
fun boxDerived(value: Derived): Box<Derived> = Box(value)
fun unboxBase(box: Box<Base>): Base = box.value
一種看似理所當(dāng)然地將這倆函數(shù)轉(zhuǎn)換成 Java 代碼的方式可能會(huì)是:
Box<Derived> boxDerived(Derived value) { …… }
Base unboxBase(Box<Base> box) { …… }
問題是往史,在 Kotlin 中我們可以這樣寫 unboxBase(boxDerived("s"))
,但是在 Java 中是行不通的佛舱,因?yàn)樵?Java 中類 Box
在其泛型參數(shù) T
上是不型變的椎例,于是 Box<Derived>
并不是 Box<Base>
的子類。 要使其在 Java 中工作请祖,我們按以下這樣定義 unboxBase
:
Base unboxBase(Box<? extends Base> box) { …… }
這里我們使用 Java 的通配符類型(? extends Base
)來通過使用處型變來模擬聲明處型變订歪,因?yàn)樵?Java 中只能這樣。
當(dāng)它作為參數(shù)出現(xiàn)時(shí)肆捕,為了讓 Kotlin 的 API 在 Java 中工作刷晋,對(duì)于協(xié)變定義的 Box
我們生成 Box<Super>
作為 Box<? extends Super>
(或者對(duì)于逆變定義的 Foo
生成 Foo<? super Bar>
)。當(dāng)它是一個(gè)返回值時(shí)慎陵, 我們不生成通配符眼虱,因?yàn)榉駝t Java 客戶端將必須處理它們(并且它違反常用 Java 編碼風(fēng)格)。因此席纽,我們的示例中的對(duì)應(yīng)函數(shù)實(shí)際上翻譯如下:
// 作為返回類型——沒有通配符
Box<Derived> boxDerived(Derived value) { …… }
// 作為參數(shù)——有通配符
Base unboxBase(Box<? extends Base> box) { …… }
注意:當(dāng)參數(shù)類型是 final 時(shí)捏悬,生成通配符通常沒有意義,所以無論在什么地方 Box<String>
始終轉(zhuǎn)換為 Box<String>
润梯。
如果我們在默認(rèn)不生成通配符的地方需要通配符过牙,我們可以使用 @JvmWildcard
注解:
fun boxDerived(value: Derived): Box<@JvmWildcard Derived> = Box(value)
// 將被轉(zhuǎn)換成
// Box<? extends Derived> boxDerived(Derived value) { …… }
另一方面,如果我們根本不需要默認(rèn)的通配符轉(zhuǎn)換纺铭,我們可以使用@JvmSuppressWildcards
fun unboxBase(box: Box<@JvmSuppressWildcards Base>): Base = box.value
// 會(huì)翻譯成
// Base unboxBase(Box<Base> box) { …… }
注意:@JvmSuppressWildcards
不僅可用于單個(gè)類型參數(shù)抒和,還可用于整個(gè)聲明(如函數(shù)或類),從而抑制其中的所有通配符彤蔽。
Nothing 類型翻譯
類型 Nothing
是特殊的摧莽,因?yàn)樗?Java 中沒有自然的對(duì)應(yīng)。確實(shí)顿痪,每個(gè) Java 引用類型镊辕,包括java.lang.Void
都可以接受 null
值油够,但是 Nothing 不行。因此征懈,這種類型不能在 Java 世界中準(zhǔn)確表示石咬。這就是為什么在使用 Nothing
參數(shù)的地方 Kotlin 生成一個(gè)原始類型:
fun emptyList(): List<Nothing> = listOf()
// 會(huì)翻譯成
// List emptyList() { …… }
在 Kotlin 中使用 JNI
要聲明一個(gè)在本地(C 或 C++)代碼中實(shí)現(xiàn)的函數(shù),你需要使用 external
修飾符來標(biāo)記它:
external fun foo(x: Int): Double
其余的過程與 Java 中的工作方式完全相同卖哎。
Kotlin 高級(jí)編程
領(lǐng)域特定語言 DSL
域特定語言(DSL)的基本思想是針對(duì)特定類型的問題的計(jì)算機(jī)語言鬼悠,而不是面向任何類型的軟件問題的通用語言。
類型安全的構(gòu)建器
構(gòu)建器(builder)的概念在 Groovy 社區(qū)中非常熱門亏娜。 構(gòu)建器允許以半聲明(semi-declarative)的方式定義數(shù)據(jù)焕窝。構(gòu)建器很適合用來生成 XML、 布局 UI 組件维贺、 描述 3D 場景以及其他更多功能……
Kotlin 允許檢查類型的構(gòu)建器官觅,比 Groovy 自身的動(dòng)態(tài)類型實(shí)現(xiàn)更具吸引力监透。
HTML DSL kotlin 官方示例:
fun main(args: Array<String>) {
val result =
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" }
// an element with attributes and text content
a() { +"Kotlin" }
// mixed content
p {
+"This is some"
b { +"mixed" }
+"text. For more see the"
a() { +"Kotlin" }
+"project"
}
p { +"some text" }
// content generated from command-line arguments
p {
+"Command line arguments were:"
ul {
for (arg in args)
li { +arg }
}
}
}
}
println(result)
}
interface Element {
fun render(builder: StringBuilder, indent: String)
}
class TextElement(val text: String) : Element {
override fun render(builder: StringBuilder, indent: String) {
builder.append("$indent$text\n")
}
}
@DslMarker
annotation class HtmlTagMarker
@HtmlTagMarker
abstract class Tag(val name: String) : Element {
val children = arrayListOf<Element>()
val attributes = hashMapOf<String, String>()
protected fun <T : Element> initTag(tag: T, init: T.() -> Unit): T {
tag.init()
children.add(tag)
return tag
}
override fun render(builder: StringBuilder, indent: String) {
builder.append("$indent<$name${renderAttributes()}>\n")
for (c in children) {
c.render(builder, indent + " ")
}
builder.append("$indent</$name>\n")
}
private fun renderAttributes(): String? {
val builder = StringBuilder()
for (a in attributes.keys) {
builder.append(" $a=\"${attributes[a]}\"")
}
return builder.toString()
}
override fun toString(): String {
val builder = StringBuilder()
render(builder, "")
return builder.toString()
}
}
abstract class TagWithText(name: String) : Tag(name) {
operator fun String.unaryPlus() {
children.add(TextElement(this))
}
}
class HTML() : TagWithText("html") {
fun head(init: Head.() -> Unit) = initTag(Head(), init)
fun body(init: Body.() -> Unit) = initTag(Body(), init)
}
class Head() : TagWithText("head") {
fun title(init: Title.() -> Unit) = initTag(Title(), init)
}
class Title() : TagWithText("title")
abstract class BodyTag(name: String) : TagWithText(name) {
fun b(init: B.() -> Unit) = initTag(B(), init)
fun p(init: P.() -> Unit) = initTag(P(), init)
fun h1(init: H1.() -> Unit) = initTag(H1(), init)
fun ul(init: UL.() -> Unit) = initTag(UL(), init)
fun a(href: String, init: A.() -> Unit) {
val a = initTag(A(), init)
a.href = href
}
}
class Body() : BodyTag("body")
class UL() : BodyTag("ul") {
fun li(init: LI.() -> Unit) = initTag(LI(), init)
}
class B() : BodyTag("b")
class LI() : BodyTag("li")
class P() : BodyTag("p")
class H1() : BodyTag("h1")
class A() : BodyTag("a") {
public var href: String
get() = attributes["href"]!!
set(value) {
attributes["href"] = value
}
}
fun html(init: HTML.() -> Unit): HTML {
val html = HTML()
html.init()
return html
}
上面實(shí)現(xiàn) HTML 標(biāo)簽,實(shí)際上是調(diào)用一個(gè) lambda函數(shù),用一個(gè)標(biāo)簽接收者的函數(shù)類型zuo作為參數(shù),使在函數(shù)內(nèi)部調(diào)用該實(shí)例的成員供鸠。
幾個(gè)厲害的 DSL 項(xiàng)目
- Anko 用于 Android 的澈侠,用于描述 UI 写妥。
- Gensokyo 用于 Swing 的拳球,用于描述 UI
- KotlinTest Kotlin測試框架基于優(yōu)秀的Scalatest
協(xié)程 Coroutine
在 Kotlin 1.1 中協(xié)程是實(shí)驗(yàn)性的。另外kotlin 為了減少程序體積,根據(jù)需要使用協(xié)程,你要加入kotlinx-coroutines-core
庫.
一些 API 啟動(dòng)長時(shí)間運(yùn)行的操作(例如網(wǎng)絡(luò) IO珍特、文件 IO祝峻、CPU 或 GPU 密集型任務(wù)等),并要求調(diào)用者阻塞直到它們完成扎筒。協(xié)程提供了一種避免阻塞線程并用更廉價(jià)莱找、更可控的操作替代線程阻塞的方法:協(xié)程掛起。
協(xié)程通過將復(fù)雜性放入庫來簡化異步編程嗜桌。程序的邏輯可以在協(xié)程中順序地表達(dá)奥溺,而底層庫會(huì)為我們解決其異步性。該庫可以將用戶代碼的相關(guān)部分包裝為回調(diào)骨宠、訂閱相關(guān)事件浮定、在不同線程(甚至不同機(jī)器!)上調(diào)度執(zhí)行层亿,而代碼則保持如同順序執(zhí)行一樣簡單桦卒。
許多在其他語言中可用的異步機(jī)制可以使用 Kotlin 協(xié)程實(shí)現(xiàn)為庫。這包括源于 C# 和 ECMAScript 的 async
/await
匿又、源于 Go 的 管道 和 select
以及源于 C# 和 Python 生成器/yield
方灾。關(guān)于提供這些結(jié)構(gòu)的庫請參見其下文描述。
阻塞 vs 掛起
基本上琳省,協(xié)程計(jì)算可以被掛起而無需阻塞線程。線程阻塞的代價(jià)通常是昂貴的躲撰,尤其在高負(fù)載時(shí)针贬,因?yàn)橹挥邢鄬?duì)少量線程實(shí)際可用,因此阻塞其中一個(gè)會(huì)導(dǎo)致一些重要的任務(wù)被延遲拢蛋。
另一方面桦他,協(xié)程掛起幾乎是無代價(jià)的。不需要上下文切換或者 OS 的任何其他干預(yù)。最重要的是快压,掛起可以在很大程度上由用戶庫控制:作為庫的作者圆仔,我們可以決定掛起時(shí)發(fā)生什么并根據(jù)需求優(yōu)化/記日志/截獲。
另一個(gè)區(qū)別是蔫劣,協(xié)程不能在隨機(jī)的指令中掛起坪郭,而只能在所謂的掛起點(diǎn)掛起,這會(huì)調(diào)用特別標(biāo)記的函數(shù)脉幢。
掛起函數(shù)
當(dāng)我們調(diào)用標(biāo)記有特殊修飾符 suspend
的函數(shù)時(shí)歪沃,會(huì)發(fā)生掛起:
suspend fun doSomething(foo: Foo): Bar {
……
}
這樣的函數(shù)稱為掛起函數(shù),因?yàn)檎{(diào)用它們可能掛起協(xié)程(如果相關(guān)調(diào)用的結(jié)果已經(jīng)可用嫌松,庫可以決定繼續(xù)進(jìn)行而不掛起)沪曙。掛起函數(shù)能夠以與普通函數(shù)相同的方式獲取參數(shù)和返回值,但它們只能從協(xié)程和其他掛起函數(shù)中調(diào)用萎羔。事實(shí)上液走,要啟動(dòng)協(xié)程,必須至少有一個(gè)掛起函數(shù)贾陷,它通常是匿名的(即它是一個(gè)掛起 lambda 表達(dá)式)缘眶。讓我們來看一個(gè)例子,一個(gè)簡化的 async()
函數(shù)(源自 kotlinx.coroutines
庫):
fun <T> async(block: suspend () -> T)
這里的 async()
是一個(gè)普通函數(shù)(不是掛起函數(shù))昵宇,但是它的 block
參數(shù)具有一個(gè)帶 suspend
修飾符的函數(shù)類型: suspend () -> T
磅崭。所以,當(dāng)我們將一個(gè) lambda 表達(dá)式傳給 async()
時(shí)瓦哎,它會(huì)是掛起 lambda 表達(dá)式砸喻,于是我們可以從中調(diào)用掛起函數(shù):
async {
doSomething(foo)
……
}
繼續(xù)該類比,await()
可以是一個(gè)掛起函數(shù)(因此也可以在一個(gè) async {}
塊中調(diào)用)蒋譬,該函數(shù)掛起一個(gè)協(xié)程割岛,直到一些計(jì)算完成并返回其結(jié)果:
async {
……
val result = computation.await()
……
}
更多關(guān)于 async/await
函數(shù)實(shí)際在 kotlinx.coroutines
中如何工作的信息可以在這里找到。
請注意犯助,掛起函數(shù) await()
和 doSomething()
不能在像 main()
這樣的普通函數(shù)中調(diào)用:
fun main(args: Array<String>) {
doSomething() // 錯(cuò)誤:掛起函數(shù)從非協(xié)程上下文調(diào)用
}
還要注意的是癣漆,掛起函數(shù)可以是虛擬的,當(dāng)覆蓋它們時(shí)剂买,必須指定 suspend
修飾符:
interface Base {
suspend fun foo()
}
class Derived: Base {
override suspend fun foo() { …… }
}
@RestrictsSuspension 注解
擴(kuò)展函數(shù)(和 lambda 表達(dá)式)也可以標(biāo)記為 suspend
惠爽,就像普通的一樣。這允許創(chuàng)建 DSL 及其他用戶可擴(kuò)展的 API瞬哼。在某些情況下婚肆,庫作者需要阻止用戶添加新方式來掛起協(xié)程。
為了實(shí)現(xiàn)這一點(diǎn)坐慰,可以使用 @RestrictsSuspension
注解较性。當(dāng)接收者類/接口 R
用它標(biāo)注時(shí),所有掛起擴(kuò)展都需要委托給 R
的成員或其它委托給它的擴(kuò)展。由于擴(kuò)展不能無限相互委托(程序不會(huì)終止)赞咙,這保證所有掛起都通過調(diào)用 R
的成員發(fā)生责循,庫的作者就可以完全控制了。
這在少數(shù)情況是需要的攀操,當(dāng)每次掛起在庫中以特殊方式處理時(shí)院仿。例如,當(dāng)通過 buildSequence()
函數(shù)實(shí)現(xiàn)下文所述的生成器時(shí)崔赌,我們需要確保在協(xié)程中的任何掛起調(diào)用最終調(diào)用 yield()
或 yieldAll()
而不是任何其他函數(shù)意蛀。這就是為什么 SequenceBuilder
用 @RestrictsSuspension
注解:
@RestrictsSuspension
public abstract class SequenceBuilder<in T> {
……
}
參見其 Github 上 的源代碼阴挣。
協(xié)程的內(nèi)部機(jī)制
我們不是在這里給出一個(gè)關(guān)于協(xié)程如何工作的完整解釋悯嗓,然而粗略地認(rèn)識(shí)發(fā)生了什么是相當(dāng)重要的。
協(xié)程完全通過編譯技術(shù)實(shí)現(xiàn)(不需要來自 VM 或 OS 端的支持)往声,掛起通過代碼來生效慈迈∪糁基本上,每個(gè)掛起函數(shù)(優(yōu)化可能適用痒留,但我們不在這里討論)都轉(zhuǎn)換為狀態(tài)機(jī)谴麦,其中的狀態(tài)對(duì)應(yīng)于掛起調(diào)用。剛好在掛起前伸头,下一狀態(tài)與相關(guān)局部變量等一起存儲(chǔ)在編譯器生成的類的字段中匾效。在恢復(fù)該協(xié)程時(shí),恢復(fù)局部變量并且狀態(tài)機(jī)從剛好掛起之后的狀態(tài)進(jìn)行恤磷。
掛起的協(xié)程可以作為保持其掛起狀態(tài)與局部變量的對(duì)象來存儲(chǔ)和傳遞面哼。這種對(duì)象的類型是 Continuation
,而這里描述的整個(gè)代碼轉(zhuǎn)換對(duì)應(yīng)于經(jīng)典的延續(xù)性傳遞風(fēng)格(Continuation-passing style)扫步。因此魔策,掛起函數(shù)有一個(gè) Continuation
類型的額外參數(shù)作為高級(jí)選項(xiàng)。
關(guān)于協(xié)程工作原理的更多細(xì)節(jié)可以在這個(gè)設(shè)計(jì)文檔中找到河胎。在其他語言(如 C# 或者 ECMAScript 2016)中的 async/await 的類似描述與此相關(guān)闯袒,雖然它們實(shí)現(xiàn)的語言功能可能不像 Kotlin 協(xié)程這樣通用。
協(xié)程的實(shí)驗(yàn)性狀態(tài)
協(xié)程的設(shè)計(jì)是實(shí)驗(yàn)性的游岳,這意味著它可能在即將發(fā)布的版本中更改政敢。當(dāng)在 Kotlin 1.1 中編譯協(xié)程時(shí),默認(rèn)情況下會(huì)報(bào)一個(gè)警告:“協(xié)程”功能是實(shí)驗(yàn)性的胚迫。要移出該警告喷户,你需要指定 opt-in 標(biāo)志。
由于其實(shí)驗(yàn)性狀態(tài)晌区,標(biāo)準(zhǔn)庫中協(xié)程相關(guān)的 API 放在 kotlin.coroutines.experimental
包下摩骨。當(dāng)設(shè)計(jì)完成并且實(shí)驗(yàn)性狀態(tài)解除時(shí),最終的 API 會(huì)移動(dòng)到 kotlin.coroutines
朗若,并且實(shí)驗(yàn)包會(huì)被保留(可能在一個(gè)單獨(dú)的構(gòu)件中)以實(shí)現(xiàn)向后兼容恼五。
重要注意事項(xiàng):我們建議庫作者遵循相同慣例:給暴露基于協(xié)程 API 的包添加“experimental”后綴(如 com.example.experimental
),以使你的庫保持二進(jìn)制兼容哭懈。當(dāng)最終 API 發(fā)布時(shí)灾馒,請按照下列步驟操作:
- 將所有 API 復(fù)制到
com.example
(沒有 experimental 后綴), - 保持實(shí)驗(yàn)包的向后兼容性遣总。
這將最小化你的用戶的遷移問題睬罗。
標(biāo)準(zhǔn) API
協(xié)程有三個(gè)主要組成部分:
- 語言支持(即如上所述的掛起功能),
- Kotlin 標(biāo)準(zhǔn)庫中的底層核心 API旭斥,
- 可以直接在用戶代碼中使用的高級(jí) API容达。
底層 API:kotlin.coroutines
底層 API 相對(duì)較小,并且除了創(chuàng)建更高級(jí)的庫之外垂券,不應(yīng)該使用它花盐。 它由兩個(gè)主要包組成:
-
kotlin.coroutines.experimental
帶有主要類型與下述原語 -
kotlin.coroutines.experimental.intrinsics
帶有甚至更底層的內(nèi)在函數(shù)如suspendCoroutineOrReturn
關(guān)于這些 API 用法的更多細(xì)節(jié)可以在這里找到。
kotlin.coroutines 中的生成器 API
kotlin.coroutines.experimental
中僅有的“應(yīng)用程序級(jí)”函數(shù)是
這些包含在 kotlin-stdlib
中因?yàn)樗麄兣c序列相關(guān)菇爪。這些函數(shù)(我們可以僅限于這里的 buildSequence()
)實(shí)現(xiàn)了 生成器 算芯,即提供一種廉價(jià)構(gòu)建惰性序列的方法:
val fibonacciSeq = buildSequence {
var a = 0
var b = 1
yield(1)
while (true) {
yield(a + b)
val tmp = a + b
a = b
b = tmp
}
}
這通過創(chuàng)建一個(gè)協(xié)程生成一個(gè)惰性的、潛在無限的斐波那契數(shù)列凳宙,該協(xié)程通過調(diào)用 yield()
函數(shù)來產(chǎn)生連續(xù)的斐波納契數(shù)熙揍。當(dāng)在這樣的序列的迭代器上迭代每一步,都會(huì)執(zhí)行生成下一個(gè)數(shù)的協(xié)程的另一部分氏涩。因此届囚,我們可以從該序列中取出任何有限的數(shù)字列表,例如 fibonacciSeq.take(8).toList()
結(jié)果是 [1, 1, 2, 3, 5, 8, 13, 21]
削葱。協(xié)程足夠廉價(jià)使這很實(shí)用奖亚。
為了演示這樣一個(gè)序列的真正惰性,讓我們在調(diào)用 buildSequence()
內(nèi)部輸出一些調(diào)試信息:
val lazySeq = buildSequence {
print("START ")
for (i in 1..5) {
yield(i)
print("STEP ")
}
print("END")
}
// 輸出序列的前三個(gè)元素
lazySeq.take(3).forEach { print("$it ") }
運(yùn)行上面的代碼看析砸,是不是我們輸出前三個(gè)元素的數(shù)字與生成循環(huán)的 STEP
有交叉昔字。這意味著計(jì)算確實(shí)是惰性的。要輸出 1
首繁,我們只執(zhí)行到第一個(gè) yield(i)
作郭,并且過程中會(huì)輸出 START
。然后弦疮,輸出 2
夹攒,我們需要繼續(xù)下一個(gè) yield(i)
,并會(huì)輸出 STEP
胁塞。3
也一樣咏尝。永遠(yuǎn)不會(huì)輸出再下一個(gè) STEP
(以及END
)压语,因?yàn)槲覀冊僖矝]有請求序列的后續(xù)元素。
為了一次產(chǎn)生值的集合(或序列)编检,可以使用 yieldAll()
函數(shù):
val lazySeq = buildSequence {
yield(0)
yieldAll(1..10)
}
lazySeq.forEach { print("$it ") }
buildIterator()
的工作方式類似于 buildSequence()
胎食,但返回一個(gè)惰性迭代器。
可以通過為 SequenceBuilder
類寫掛起擴(kuò)展(帶有上文描述的 @RestrictsSuspension
注解)來為 buildSequence()
添加自定義生產(chǎn)邏輯(custom yielding logic):
suspend fun SequenceBuilder<Int>.yieldIfOdd(x: Int) {
if (x % 2 != 0) yield(x)
}
val lazySeq = buildSequence {
for (i in 1..10) yieldIfOdd(i)
}
其他高級(jí) API:kotlinx.coroutines
只有與協(xié)程相關(guān)的核心 API 可以從 Kotlin 標(biāo)準(zhǔn)庫獲得允懂。這主要包括所有基于協(xié)程的庫可能使用的核心原語和接口厕怜。
大多數(shù)基于協(xié)程的應(yīng)用程序級(jí)API都作為單獨(dú)的庫發(fā)布:kotlinx.coroutines
。這個(gè)庫覆蓋了
- 使用
kotlinx-coroutines-core
的平臺(tái)無關(guān)異步編程
此模塊包括支持
select
和其他便利原語的類似 Go 的管道這個(gè)庫的綜合指南在這里蕾总。
基于 JDK 8 中的
CompletableFuture
的 API:kotlinx-coroutines-jdk8
基于 JDK 7 及更高版本 API 的非阻塞 IO(NIO):
kotlinx-coroutines-nio
支持 Swing (
kotlinx-coroutines-swing
) 和 JavaFx (kotlinx-coroutines-javafx
)支持 RxJava:
kotlinx-coroutines-rx
這些庫既作為使通用任務(wù)易用的便利的 API粥航,也作為如何構(gòu)建基于協(xié)程的庫的端到端示例。
更多
集合
與大多數(shù)語言不同生百,Kotlin 區(qū)分可變集合和不可變集合(lists递雀、sets、maps 等)蚀浆。精確控制什么時(shí)候集合可編輯有助于消除 bug 和設(shè)計(jì)良好的 API映之。
預(yù)先了解一個(gè)可變集合的只讀 視圖 和一個(gè)真正的不可變集合之間的區(qū)別是很重要的。它們都容易創(chuàng)建蜡坊,但類型系統(tǒng)不能表達(dá)它們的差別杠输,所以由你來跟蹤(是否相關(guān))。
Kotlin 的 List<out T>
類型是一個(gè)提供只讀操作如 size
秕衙、get
等的接口蠢甲。和 Java 類似,它繼承自 Collection<T>
進(jìn)而繼承自 Iterable<T>
据忘。改變 list 的方法是由 MutableList<T>
加入的鹦牛。這一模式同樣適用于 Set<out T>/MutableSet<T>
及 Map<K, out V>/MutableMap<K, V>
。
我們可以看下 list 及 set 類型的基本用法:
val numbers: MutableList<Int> = mutableListOf(1, 2, 3)
val readOnlyView: List<Int> = numbers
println(numbers) // 輸出 "[1, 2, 3]"
numbers.add(4)
println(readOnlyView) // 輸出 "[1, 2, 3, 4]"
readOnlyView.clear() // -> 不能編譯
val strings = hashSetOf("a", "b", "c", "c")
assert(strings.size == 3)
Kotlin 沒有專門的語法結(jié)構(gòu)創(chuàng)建 list 或 set勇吊。 要用標(biāo)準(zhǔn)庫的方法曼追,如 listOf()
、 mutableListOf()
汉规、 setOf()
礼殊、 mutableSetOf()
。 在非性能關(guān)鍵代碼中創(chuàng)建 map 可以用一個(gè)簡單的慣用法來完成:mapOf(a to b, c to d)
针史。
注意上面的 readOnlyView
變量(譯者注:與對(duì)應(yīng)可變集合變量 numbers
)指向相同的底層 list 并會(huì)隨之改變晶伦。 如果一個(gè) list 只存在只讀引用,我們可以考慮該集合完全不可變啄枕。創(chuàng)建一個(gè)這樣的集合的一個(gè)簡單方式如下:
val items = listOf(1, 2, 3)
目前 listOf
方法是使用 array list 實(shí)現(xiàn)的婚陪,但是未來可以利用它們知道自己不能變的事實(shí),返回更節(jié)約內(nèi)存的完全不可變的集合類型频祝。
注意這些類型是協(xié)變的泌参。這意味著脆淹,你可以把一個(gè) List<Rectangle>
賦值給 List<Shape>
假定 Rectangle 繼承自 Shape。對(duì)于可變集合類型這是不允許的沽一,因?yàn)檫@將導(dǎo)致運(yùn)行時(shí)故障未辆。
有時(shí)你想給調(diào)用者返回一個(gè)集合在某個(gè)特定時(shí)間的一個(gè)快照, 一個(gè)保證不會(huì)變的:
class Controller {
private val _items = mutableListOf<String>()
val items: List<String> get() = _items.toList()
}
這個(gè) toList
擴(kuò)展方法只是復(fù)制列表項(xiàng),因此返回的 list 保證永遠(yuǎn)不會(huì)改變锯玛。
List 和 set 有很多有用的擴(kuò)展方法值得熟悉:
val items = listOf(1, 2, 3, 4)
items.first() == 1
items.last() == 4
items.filter { it % 2 == 0 } // 返回 [2, 4]
val rwList = mutableListOf(1, 2, 3)
rwList.requireNoNulls() // 返回 [1, 2, 3]
if (rwList.none { it > 6 }) println("No items above 6") // 輸出“No items above 6”
val item = rwList.firstOrNull()
…… 以及所有你所期望的實(shí)用工具,例如 sort兼蜈、zip攘残、fold、reduce 等等为狸。
Map 遵循同樣模式歼郭。它們可以容易地實(shí)例化和訪問,像這樣:
val readWriteMap = hashMapOf("foo" to 1, "bar" to 2)
println(readWriteMap["foo"]) // 輸出“1”
val snapshot: Map<String, Int> = HashMap(readWriteMap)
類型安全和智能轉(zhuǎn)換
空安全
Kotlin 的類型系統(tǒng)
- 可空類型
- 非空類型
它消除了很多編程語言(如: Java)來自于代碼空引用,而導(dǎo)致的 NullPointerException
或簡稱 NPE
辐棒。
NOTE: Kotlin 發(fā)生 NPE 原因可能如下:
- 顯式調(diào)用
throw NullPointerException()
- 使用了下文描述的
!!
操作符 - 外部 Java 代碼導(dǎo)致的
在上面 變量 中, Kotlin 默認(rèn)聲明變量時(shí)是非空類型的,要使該變量接收 null
值病曾,需使用 ?
操作符 , 例子如下
var aNullNothing = null
var bNullUnable: Int = null //不能為空
var cNullUnable = 1 //不能為空
var cNullable: Int? = null //能為空
var dNullable: Any? = 1 //能為空
fun fun0(): Unit {
aNullNothing = 1 //Nothing error
cNullUnable = null
cNullable = 1
dNullable = null //可以 null
}
當(dāng)聲明可空類型變量時(shí),它是不安全的,訪問方法或?qū)傩詴r(shí)需要作處理:
- 在條件中檢查 null ,但僅適用于
val
且不可覆蓋(即不能用 open 修飾)或者get
的不可變的變量漾根。 - 安全的調(diào)用
?.
, 若為null 則跳過,否則接著調(diào)用 - !! 操作符 ,會(huì)返回一個(gè)非空的值,否則拋出一個(gè)
NPE
異常
條件中檢查 nul 例子
open class TestCheckNull {
val cReadNullable: Int? = 1
val cGetReadNullable: Int? get() = 1
open val cOverrideReadNullable: Int? = 1
fun fun0(): Unit {
if (cReadNullable != null) {
cReadNullable.dec() //tips replace safe access expression
}
if (cGetReadNullable != null) {
cGetReadNullable.dec()
}
if (cOverrideReadNullable != null) {
cOverrideReadNullable.dec()
}
}
}
安全調(diào)用和!! 操作符對(duì)比
cNullUnable.dec() //保證不會(huì)導(dǎo)致 NPE
val hc = dNullable?.hashCode() //dNullable == null return null, hc is null
val dec = cNullable?.dec() // cNullable !=null return cNullable.dec(),dec is "0"
cNullable!!.dec() // cNullable !=null execute dec()
dNullable!!.toString() // dNullable == null throws NPE
var aNotNullObject = cNullable!!
類型檢測和安全的類型轉(zhuǎn)換
-
is
!is
運(yùn)算符檢測一個(gè)表達(dá)式是否某類型的一個(gè)實(shí)例泰涂。在許多情況下,不需要在 Kotlin 中使用顯式轉(zhuǎn)換操作符辐怕,因?yàn)榫幾g器跟蹤不可變值的is
-檢查逼蒙,并在需要時(shí)自動(dòng)插入(安全的)轉(zhuǎn)換:
val obj: Any = ""
if (obj is String) {
print(obj.length)
}
if (obj !is String) { // 與 !(obj is String) 相同
print("Not a String")
} else if (obj is String) {
print(obj.length)
} else {
print(obj.length)
}
when(obj){
is String -> obj.length
}
-
as
as?
運(yùn)算符能把對(duì)象轉(zhuǎn)換為目標(biāo)類型,常規(guī)類型轉(zhuǎn)換可能會(huì)導(dǎo)致ClassCastException
寄疏。使用安全的類型轉(zhuǎn)換符as?
是牢,如果嘗試轉(zhuǎn)換不成功則返回 null:
val father = Father()
val son = Son()
println(father is Son)
println(son is Father)
val fatherSon: Father = Son()
println(fatherSon is Son)
println(fatherSon is Father)
val sonFatherSon: Son = fatherSon as Son
println(sonFatherSon != null)
val newFather: Son? = father as? Son
val newFather1 = father as? Son //newFather1 start define val newFather : Son?
val newFather2 = father as Son // newFather1 start define val newFather : Son
println(newFather == null)
NOTE: Kotlin 類型檢測十分智能, 想了解請更多參考 Type Checks and Casts
操作符重載
Kotlin 允許我們?yōu)樽约旱念愋吞峁╊A(yù)定義的一組操作符的實(shí)現(xiàn)。這些操作符具有固定的符號(hào)表示(如 +
或 *
)和固定的優(yōu)先級(jí)陕截。為實(shí)現(xiàn)這樣的操作符驳棱,我們?yōu)橄鄳?yīng)的類型(即二元操作符左側(cè)的類型和一元操作符的參數(shù)類型)提供了一個(gè)固定名字的成員函數(shù)或擴(kuò)展函數(shù)。
重載操作符的函數(shù)需要用 operator
修飾符標(biāo)記农曲。
另外劝贸,我們描述為不同操作符規(guī)范操作符重載的約定。
一元前綴操作符
表達(dá)式 | 翻譯為 |
---|---|
+a | a.unaryPlus() |
-a | a.unaryMinus() |
!a | a.not() |
這個(gè)表是說脱羡,當(dāng)編譯器處理例如表達(dá)式 +a
時(shí)捏膨,它執(zhí)行以下步驟:
- 確定
a
的類型,令其為T
驯妄。 - 為接收者
T
查找一個(gè)帶有operator
修飾符的無參函數(shù)unaryPlus()
荷并,即成員函數(shù)或擴(kuò)展函數(shù)。 - 如果函數(shù)不存在或不明確青扔,則導(dǎo)致編譯錯(cuò)誤源织。
- 如果函數(shù)存在且其返回類型為
R
翩伪,那就表達(dá)式+a
具有類型R
。
注意 這些操作以及所有其他操作都針對(duì)基本類型做了優(yōu)化谈息,不會(huì)為它們引入函數(shù)調(diào)用的開銷缘屹。
以下是如何重載一元減運(yùn)算符的示例:
data class Point(val x: Int, val y: Int)
operator fun Point.unaryMinus() = Point(-x, -y)
val point = Point(10, 20)
println(-point) // 輸出“(-10, -20)”
遞增與遞減
表達(dá)式 | 翻譯為 |
---|---|
a++ | a.inc() + 見下文 |
a-- | a.dec() + 見下文 |
inc()
和 dec()
函數(shù)必須返回一個(gè)值,它用于賦值給使用++
或 --
操作的變量侠仇。它們不應(yīng)該改變在其上調(diào)用 inc()
或 dec()
的對(duì)象互亮。
編譯器執(zhí)行以下步驟來解析后綴形式的操作符,例如 a++
:
- 確定
a
的類型序六,令其為T
。 - 查找一個(gè)適用于類型為
T
的接收者的、帶有operator
修飾符的無參數(shù)函數(shù)inc()
峻凫。 - 檢查函數(shù)的返回類型是
T
的子類型渗鬼。
計(jì)算表達(dá)式的步驟是:
- 把
a
的初始值存儲(chǔ)到臨時(shí)存儲(chǔ)a0
中, - 把
a.inc()
結(jié)果賦值給a
荧琼, - 把
a0
作為表達(dá)式的結(jié)果返回譬胎。
對(duì)于 a--
,步驟是完全類似的命锄。
對(duì)于前綴形式 ++a
和 --a
以相同方式解析堰乔,其步驟是:
- 把
a.inc()
結(jié)果賦值給a
, - 把
a
的新值作為表達(dá)式結(jié)果返回脐恩。
二元操作算術(shù)運(yùn)算符
表達(dá)式 | 翻譯為 |
---|---|
a + b | a.plus(b) |
a - b | a.minus(b) |
a * b | a.times(b) |
a / b | a.div(b) |
a % b | a.rem(b)镐侯、 a.mod(b) (已棄用) |
a..b | a.rangeTo(b) |
對(duì)于此表中的操作,編譯器只是解析成翻譯為列中的表達(dá)式驶冒。
請注意苟翻,自 Kotlin 1.1 起支持 rem
運(yùn)算符。Kotlin 1.0 使用 mod
運(yùn)算符骗污,它在
Kotlin 1.1 中被棄用崇猫。
示例
下面是一個(gè)從給定值起始的 Counter 類的示例,它可以使用重載的 +
運(yùn)算符來增加計(jì)數(shù)需忿。
data class Counter(val dayIndex: Int) {
operator fun plus(increment: Int): Counter {
return Counter(dayIndex + increment)
}
}
In操作符
表達(dá)式 | 翻譯為 |
---|---|
a in b | b.contains(a) |
a !in b | !b.contains(a) |
對(duì)于 in
和 !in
诅炉,過程是相同的,但是參數(shù)的順序是相反的屋厘。
索引訪問操作符
表達(dá)式 | 翻譯為 |
---|---|
a[i] | a.get(i) |
a[i, j] | a.get(i, j) |
a[i_1, ……, i_n] | a.get(i_1, ……, i_n) |
a[i] = b | a.set(i, b) |
a[i, j] = b | a.set(i, j, b) |
a[i_1, ……, i_n] = b | a.set(i_1, ……, i_n, b) |
方括號(hào)轉(zhuǎn)換為調(diào)用帶有適當(dāng)數(shù)量參數(shù)的 get
和 set
涕烧。
調(diào)用操作符
表達(dá)式 | 翻譯為 |
---|---|
a() | a.invoke() |
a(i) | a.invoke(i) |
a(i, j) | a.invoke(i, j) |
a(i_1, ……, i_n) | a.invoke(i_1, ……, i_n) |
圓括號(hào)轉(zhuǎn)換為調(diào)用帶有適當(dāng)數(shù)量參數(shù)的 invoke
。
廣義賦值
表達(dá)式 | 翻譯為 |
---|---|
a += b | a.plusAssign(b) |
a -= b | a.minusAssign(b) |
a *= b | a.timesAssign(b) |
a /= b | a.divAssign(b) |
a %= b | a.remAssign(b), a.modAssign(b)(已棄用) |
對(duì)于賦值操作汗洒,例如 a += b
澈魄,編譯器執(zhí)行以下步驟:
- 如果右列的函數(shù)可用
- 如果相應(yīng)的二元函數(shù)(即
plusAssign()
對(duì)應(yīng)于plus()
)也可用,那么報(bào)告錯(cuò)誤(模糊)仲翎。 - 確保其返回類型是
Unit
痹扇,否則報(bào)告錯(cuò)誤。 - 生成
a.plusAssign(b)
的代碼
- 如果相應(yīng)的二元函數(shù)(即
- 否則試著生成
a = a + b
的代碼(這里包含類型檢查:a + b
的類型必須是a
的子類型)溯香。
注意:賦值在 Kotlin 中不是表達(dá)式鲫构。
相等與不等操作符
表達(dá)式 | 翻譯為 |
---|---|
a == b | a?.equals(b) ?: (b === null) |
a != b | !(a?.equals(b) ?: (b === null )) |
注意:===
和 !==
(同一性檢查)不可重載,因此不存在對(duì)他們的約定
這個(gè) ==
操作符有些特殊:它被翻譯成一個(gè)復(fù)雜的表達(dá)式玫坛,用于篩選 null
值结笨。
null == null
總是 true,對(duì)于非空的 x
,x == null
總是 false 而不會(huì)調(diào)用 x.equals()
炕吸。
比較操作符
表達(dá)式 | 翻譯為 |
---|---|
a > b | a.compareTo(b) > 0 |
a < b | a.compareTo(b) < 0 |
a >= b | a.compareTo(b) >= 0 |
a <= b | a.compareTo(b) <= 0 |
所有的比較都轉(zhuǎn)換為對(duì) compareTo
的調(diào)用伐憾,這個(gè)函數(shù)需要返回 Int
值
屬性委托操作符
provideDelegate
、 getValue
以及 setValue
操作符函數(shù)已在委托屬性中描述赫模。
命名函數(shù)的中綴調(diào)用
我們可以通過中綴函數(shù)的調(diào)用 來模擬自定義中綴操作符树肃。
類型相等性
Kotlin 中有兩種類型的相等性:
- 引用相等(兩個(gè)引用指向同一對(duì)象)
- 結(jié)構(gòu)相等(用 equals() 檢查)
引用相等
引用相等由 ===
(以及其否定形式 !==
)操作判斷。a === b
當(dāng)且僅當(dāng) a 和 b 指向同一個(gè)對(duì)象時(shí)求值為 true瀑罗。
結(jié)構(gòu)相等
結(jié)構(gòu)相等由 ==
(以及其否定形式 !=
)操作判斷胸嘴。按照慣例,像 a == b
這樣的表達(dá)式會(huì)翻譯成
a?.equals(b) ?: (b === null)
也就是說如果 a
不是 null
則調(diào)用 equals(Any?)
函數(shù)斩祭,否則(即 a
是 null
)檢查 b
是否與 null
引用相等劣像。
請注意,當(dāng)與 null
顯式比較時(shí)完全沒必要優(yōu)化你的代碼:a == null
會(huì)被自動(dòng)轉(zhuǎn)換為 a=== null
摧玫。同類型才有可比性耳奕。
This表達(dá)式
為了表示當(dāng)前的 接收者 我們使用 this 表達(dá)式:
- 在類的成員中,this 指的是該類的當(dāng)前對(duì)象
- 在擴(kuò)展函數(shù)或者帶接收者的函數(shù)字面值中诬像, this 表示在點(diǎn)左側(cè)傳遞的 接收者 參數(shù)屋群。
如果 this 沒有限定符,它指的是最內(nèi)層的包含它的作用域颅停。要引用其他作用域中的 this谓晌,請使用 標(biāo)簽限定符:
fun main(args: Array<String>) {
val kotlinThisExpression = KotlinThisExpression()
println(kotlinThisExpression.leftReference() === kotlinThisExpression)
kotlinThisExpression.InnerKotlinThisExpression().test()
}
private class KotlinThisExpression {
val thisClassObject get() = this
inner class KotlinThisExpression {
//val thisClassObject get() = this@KotlinThisExpression //不明確label
val thisClassObject get() = this //內(nèi)部類名相同,不能用限定的 this
}
inner class InnerKotlinThisExpression { // 隱式標(biāo)簽 @InnerKotlinThisExpression
fun InnerKotlinThisExpression.fuck() { // 隱式標(biāo)簽 @fuck
val a = this@KotlinThisExpression // KotlinThisExpression 的 this
val b = this@InnerKotlinThisExpression // InnerKotlinThisExpression 的 this
val c = this // fuck() 的接收者掠拳,一個(gè) InnerKotlinThisExpression
val d = this@fuck // fuck() 的接收者癞揉,一個(gè) InnerKotlinThisExpression
val label = label@ fun String.() {
println(this)// label 的接收者
}
"label".label()
val lambda = { ->
// fuck() 的接收者,因?yàn)樗?lambda 表達(dá)式
// 沒有任何接收者
println(this)
}
lambda()
}
fun test() {
fuck()
}
}
}
private fun KotlinThisExpression.leftReference() = this.thisClassObject //this 表示在點(diǎn)左側(cè)傳遞的 接收者 參數(shù)溺欧。
Nothing 類型
如果用 null
來初始化一個(gè)要推斷類型的值喊熟,而又沒有其他信息可用于確定更具體的類型時(shí),編譯器會(huì)推斷出 Nothing?
類型:
val nothingInt/*: Nothing?*/ = null
val list:List<Nothing?> = listOf(null)
另外Kotlin 中 throw
是表達(dá)式, 表達(dá)式的類型是特殊類型 Nothing
姐刁。 該類型沒有值芥牌,而是用于標(biāo)記永遠(yuǎn)不能達(dá)到的代碼位置。Nothing
可以用來標(biāo)記一個(gè)永遠(yuǎn)不會(huì)返回的函數(shù), 也可以作為 Elvis 表達(dá)式的一部分:
val nothingInt/*: Nothing?*/ = null
val list: List<Nothing?> = listOf(null)
fun fail(message: String): Nothing {
throw IllegalArgumentException(message)
}
fail("fail")
//作為 Elvis 表達(dá)式的一部分
var exception = null ?: throw RuntimeException("throw")
解構(gòu)聲明
解構(gòu)聲明是創(chuàng)建多個(gè)變量與對(duì)象componentN
函數(shù)對(duì)應(yīng)起來聂使。例如在上面的數(shù)據(jù)類中
val (name, age) = KotlinDataClass.User("Lisa", 18)
NOTE: componentN()
函數(shù)需要用 operator
關(guān)鍵字標(biāo)記壁拉,以允許在解構(gòu)聲明中使用它們。它可以用for-循環(huán)柏靶、
map-映射, 以及 lambda 表達(dá)式中弃理。
fun main(args: Array<String>) {
val (name, age) = KotlinDeconstruction.Person("jack", 32)
println("$name $age")
val request = KotlinDeconstruction.request()
val (rs, code) = request
println("result = $rs , code = $code")
//下劃線用于未使用的變量
val (_, responseCode) = request
println(responseCode)
println(request.component1())
println(request.component2())
//解構(gòu)聲明和Map
val map = mutableMapOf<String, String>()
for (it in 1..10) {
map.put(it.toString(), it.toString())
}
for ((k, v) in map) {
println("map key = $k, value = $v")
}
map.mapValues { entry -> println("key = ${entry.key}, value = ${entry.value}!") }
map.mapValues { (key, value) -> println("key = $key, value = $value!") }
}
private class KotlinDeconstruction {
class Person(val name: String, val age: Int) {
operator fun component1(): Any = name
operator fun component2(): Any = age
}
data class Response(val result: String, val code: Int)
companion object {
fun request(): Response {
//request network
return Response("ok", 200)
}
}
}
解構(gòu)聲明的好處, 如request 函數(shù)時(shí)要返回兩個(gè)東西時(shí),用它爽爆了。因?yàn)榫幾g器始終會(huì)創(chuàng)建多個(gè)變量接收,效率并不比之前用對(duì)象的高屎蜓。但實(shí)際上并不需要解析一個(gè)對(duì)象里的大量變量,否則通過對(duì)象 .
屬性獲取值痘昌。
val (name, age) = person //編譯器會(huì)生成如下兩句代碼
val name = person.component1()
val age = person.component2()
相等性
Kotlin 中有兩種類型的相等性:
- 引用相等(兩個(gè)引用指向同一對(duì)象)
- 結(jié)構(gòu)相等(用
equals()
檢查)
引用相等
引用相等由 ===
(以及其否定形式 !==
)操作判斷。a === b
當(dāng)且僅當(dāng) a
和 b
指向同一個(gè)對(duì)象時(shí)求值為 true。
結(jié)構(gòu)相等
結(jié)構(gòu)相等由 ==
(以及其否定形式 !=
)操作判斷辆苔。按照慣例算灸,像 a == b
這樣的表達(dá)式會(huì)翻譯成
a?.equals(b) ?: (b === null)
也就是說如果 a
不是 null
則調(diào)用 equals(Any?)
函數(shù),否則(即 a
是 null
)檢查 b 是否與 null
引用相等驻啤。
請注意菲驴,當(dāng)與 null
顯式比較時(shí)完全沒必要優(yōu)化你的代碼:a == null
會(huì)被自動(dòng)轉(zhuǎn)換為 a=== null
。
浮點(diǎn)數(shù)相等性
當(dāng)相等性檢測的兩個(gè)操作數(shù)都是靜態(tài)已知的(可空或非空的)Float
或 Double
類型時(shí)街佑,該檢測遵循 IEEE 754 浮點(diǎn)數(shù)運(yùn)算標(biāo)準(zhǔn)谢翎。
否則會(huì)使用不符合該標(biāo)準(zhǔn)的結(jié)構(gòu)相等性檢測,這會(huì)導(dǎo)致 NaN
等于其自身沐旨,而 -0.0
不等于 0.0
森逮。
異常
異常類
Kotlin 中所有異常類都是 Throwable
類的子孫類。 每個(gè)異常都有消息磁携、堆棸啵回溯信息和可選的原因。
使用 throw-表達(dá)式來拋出異常:
throw MyException("Hi There!")
使用 try-表達(dá)式來捕獲異常:
try {
// 一些代碼
}
catch (e: SomeException) {
// 處理程序
}
finally {
// 可選的 finally 塊
}
可以有零到多個(gè) catch 塊谊迄。finally 塊可以省略闷供。 但是 catch 和 finally 塊至少應(yīng)該存在一個(gè)。
Try 是一個(gè)表達(dá)式
try 是一個(gè)表達(dá)式统诺,即它可以有一個(gè)返回值:
val a: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }
try-表達(dá)式的返回值是 try 塊中的最后一個(gè)表達(dá)式或者是(所有)catch 塊中的最后一個(gè)表達(dá)式歪脏。 finally 塊中的內(nèi)容不會(huì)影響表達(dá)式的結(jié)果。
受檢的異常
Kotlin 沒有受檢的異常粮呢。這其中有很多原因婿失,但我們會(huì)提供一個(gè)簡單的例子。
以下是 JDK 中 StringBuilder
類實(shí)現(xiàn)的一個(gè)示例接口:
Appendable append(CharSequence csq) throws IOException;
這個(gè)簽名是什么意思啄寡? 它是說豪硅,每次我追加一個(gè)字符串到一些東西(一個(gè) StringBuilder
、某種日志挺物、一個(gè)控制臺(tái)等)上時(shí)我就必須捕獲那些 IOException
懒浮。 為什么?因?yàn)樗赡苷趫?zhí)行 IO 操作(Writer
也實(shí)現(xiàn)了 Appendable
)…… 所以它導(dǎo)致這種代碼隨處可見的出現(xiàn):
try {
log.append(message)
}
catch (IOException e) {
// 必須要安全
}
這并不好识藤,參見《Effective Java》 第 65 條:不要忽略異常砚著。
Bruce Eckel 在《Java 是否需要受檢的異常?》(Does Java need Checked Exceptions?) 中指出:
通過一些小程序測試得出的結(jié)論是異常規(guī)范會(huì)同時(shí)提高開發(fā)者的生產(chǎn)力和代碼質(zhì)量痴昧,但是大型軟件項(xiàng)目的經(jīng)驗(yàn)表明一個(gè)不同的結(jié)論——生產(chǎn)力降低稽穆、代碼質(zhì)量很少或沒有提高。
其他相關(guān)引證:
- 《Java 的受檢異常是一個(gè)錯(cuò)誤》(Java's checked exceptions were a mistake)(Rod Waldhoff)
- 《受檢異常的煩惱》(The Trouble with Checked Exceptions)(Anders Hejlsberg)
注意:throw
表達(dá)式的類型是特殊類型 Nothing
剪个。參見Nothing類型
反射
反射是這樣的一組語言和庫功能秧骑,它允許在運(yùn)行時(shí)自省你的程序的結(jié)構(gòu)版确。 Kotlin 讓語言中的函數(shù)和屬性做為一等公民、并對(duì)其自屎跽邸(即在運(yùn)行時(shí)獲悉一個(gè)名稱或者一個(gè)屬性或函數(shù)的類型)與簡單地使用函數(shù)式或響應(yīng)式風(fēng)格緊密相關(guān)绒疗。
在 Java 平臺(tái)上,使用反射功能所需的運(yùn)行時(shí)組件作為單獨(dú)的 JAR 文件(
kotlin-reflect.jar
)分發(fā)骂澄。這樣做是為了減少不使用反射功能的應(yīng)用程序所需的運(yùn)行時(shí)庫的大小吓蘑。如果你需要使用反射,請確保該 .jar文件添加到項(xiàng)目的 classpath 中坟冲。
類引用
最基本的反射功能是獲取 Kotlin 類的運(yùn)行時(shí)引用磨镶。要獲取對(duì)靜態(tài)已知的 Kotlin 類的引用,可以使用 類字面值 語法:
val c = MyClass::class
該引用是 KClass 類型的值健提。
請注意弧哎,Kotlin 類引用與 Java 類引用不同岖瑰。要獲得 Java 類引用, 請?jiān)?KClass
實(shí)例上使用 .java
屬性。
綁定的類引用(自 1.1 起)
通過使用對(duì)象作為接收者丈牢,可以用相同的 ::class
語法獲取指定對(duì)象的類的引用:
val widget: Widget = ……
assert(widget is GoodWidget) { "Bad widget: ${widget::class.qualifiedName}" }
你可以獲取對(duì)象的精確類的引用嵌言,例如 GoodWidget
或 BadWidget
窄绒,盡管接收者表達(dá)式的類型是 Widget
汞贸。
函數(shù)引用
當(dāng)我們有一個(gè)命名函數(shù)聲明如下:
fun isOdd(x: Int) = x % 2 != 0
我們可以很容易地直接調(diào)用它(isOdd(5)
),但是我們也可以把它作為一個(gè)值傳遞暗膜。例如傳給另一個(gè)函數(shù)匀奏。 為此,我們使用 ::
操作符:
val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd)) // 輸出 [1, 3]
這里 ::isOdd
是函數(shù)類型 (Int) -> Boolean
的一個(gè)值学搜。
當(dāng)上下文中已知函數(shù)期望的類型時(shí)娃善,::
可以用于重載函數(shù)。 例如:
fun isOdd(x: Int) = x % 2 != 0
fun isOdd(s: String) = s == "brillig" || s == "slithy" || s == "tove"
val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd)) // 引用到 isOdd(x: Int)
或者恒水,你可以通過將方法引用存儲(chǔ)在具有顯式指定類型的變量中來提供必要的上下文:
val predicate: (String) -> Boolean = ::isOdd // 引用到 isOdd(x: String)
如果我們需要使用類的成員函數(shù)或擴(kuò)展函數(shù)会放,它需要是限定的饲齐。 例如 String::toCharArray
為類型 String
提供了一個(gè)擴(kuò)展函數(shù):String.() -> CharArray
钉凌。
示例:函數(shù)組合
考慮以下函數(shù):
fun <A, B, C> compose(f: (B) -> C, g: (A) -> B): (A) -> C {
return { x -> f(g(x)) }
}
它返回一個(gè)傳給它的兩個(gè)函數(shù)的組合:compose(f, g) = f(g(*))
。 現(xiàn)在捂人,你可以將其應(yīng)用于可調(diào)用引用:
fun length(s: String) = s.length
val oddLength = compose(::isOdd, ::length)
val strings = listOf("a", "ab", "abc")
println(strings.filter(oddLength)) // 輸出 "[a, abc]"
屬性引用
要把屬性作為 Kotlin中 的一等對(duì)象來訪問御雕,我們也可以使用 ::
運(yùn)算符:
var x = 1
fun main(args: Array<String>) {
println(::x.get()) // 輸出 "1"
::x.set(2)
println(x) // 輸出 "2"
}
表達(dá)式 ::x
求值為 KProperty<Int>
類型的屬性對(duì)象,它允許我們使用 get()
讀取它的值滥搭,或者使用 name
屬性來獲取屬性名酸纲。更多信息請參見關(guān)于 KProperty
類的文檔。
對(duì)于可變屬性瑟匆,例如 var y = 1
闽坡,::y
返回 KMutableProperty
類型的一個(gè)值, 該類型有一個(gè) set()
方法。
屬性引用可以用在不需要參數(shù)的函數(shù)處:
val strs = listOf("a", "bc", "def")
println(strs.map(String::length)) // 輸出 [1, 2, 3]
要訪問屬于類的成員的屬性疾嗅,我們這樣限定它:
class A(val p: Int)
fun main(args: Array<String>) {
val prop = A::p
println(prop.get(A(1))) // 輸出 "1"
}
對(duì)于擴(kuò)展屬性:
val String.lastChar: Char
get() = this[length - 1]
fun main(args: Array<String>) {
println(String::lastChar.get("abc")) // 輸出 "c"
}
與 Java 反射的互操作性
在Java平臺(tái)上外厂,標(biāo)準(zhǔn)庫包含反射類的擴(kuò)展,它提供了與 Java 反射對(duì)象之間映射(參見 kotlin.reflect.jvm
包)代承。 例如汁蝶,要查找一個(gè)用作 Kotlin 屬性 getter 的 幕后字段或 Java方法,可以這樣寫:
import kotlin.reflect.jvm.*
class A(val p: Int)
fun main(args: Array<String>) {
println(A::p.javaGetter) // 輸出 "public final int A.getP()"
println(A::p.javaField) // 輸出 "private final int A.p"
}
要獲得對(duì)應(yīng)于 Java 類的 Kotlin 類论悴,請使用 .kotlin
擴(kuò)展屬性:
fun getKClass(o: Any): KClass<Any> = o.javaClass.kotlin
構(gòu)造函數(shù)引用
構(gòu)造函數(shù)可以像方法和屬性那樣引用掖棉。他們可以用于期待這樣的函數(shù)類型對(duì)象的任何地方:它與該構(gòu)造函數(shù)接受相同參數(shù)并且返回相應(yīng)類型的對(duì)象。 通過使用 ::
操作符并添加類名來引用構(gòu)造函數(shù)膀估♂:ィ考慮下面的函數(shù), 它期待一個(gè)無參并返回 Foo
類型的函數(shù)參數(shù):
class Foo
fun function(factory: () -> Foo) {
val x: Foo = factory()
}
使用 ::Foo
察纯,類 Foo 的零參數(shù)構(gòu)造函數(shù)紫谷,我們可以這樣簡單地調(diào)用它:
function(::Foo)
綁定的函數(shù)與屬性引用(自 1.1 起)
你可以引用特定對(duì)象的實(shí)例方法。
val numberRegex = "\\d+".toRegex()
println(numberRegex.matches("29")) // 輸出“true”
val isNumber = numberRegex::matches
println(isNumber("29")) // 輸出“true”
取代直接調(diào)用方法 matches
的是我們存儲(chǔ)其引用捐寥。 這樣的引用會(huì)綁定到其接收者上笤昨。 它可以直接調(diào)用(如上例所示)或者用于任何期待一個(gè)函數(shù)類型表達(dá)式的時(shí)候:
val strings = listOf("abc", "124", "a70")
println(strings.filter(numberRegex::matches)) // 輸出“[124]”
比較綁定的類型和相應(yīng)的未綁定類型的引用。 綁定的可調(diào)用引用有其接收者“附加”到其上握恳,因此接收者的類型不再是參數(shù):
val isNumber: (CharSequence) -> Boolean = numberRegex::matches
val matches: (Regex, CharSequence) -> Boolean = Regex::matches
屬性引用也可以綁定:
val prop = "abc"::length
println(prop.get()) // 輸出“3”
類型別名
類型別名為現(xiàn)有類型提供替代名稱瞒窒。 如果類型名稱太長,你可以另外引入較短的名稱乡洼,并使用新的名稱替代原類型名崇裁。可以為函數(shù)類型提供另外的別名束昵,也可以為內(nèi)部類和嵌套類創(chuàng)建新名稱
類型別名不會(huì)引入新類型拔稳。 它們等效于相應(yīng)的底層類型。 當(dāng)你在代碼中添加 typealias Predicate<T>
并使用 Predicate<Int>
時(shí)锹雏,Kotlin 編譯器總是把它擴(kuò)展為 (Int) -> Boolean
巴比。
fun main(args: Array<String>) {
val net: Net = Network()
val enable = enable(net) {
netStatus() == 0
}
println(enable)
val p: Predicate<Int> = { it > 0 }
println(listOf(1, -2).filter(p)) // 輸出 "[1]"
}
typealias Net = Network
typealias Node = Network.Node
typealias NodeSet = Set<Network.Node>
typealias FileTable<N> = MutableMap<N, MutableList<File>>
typealias MyHandler = (Int, String, Any) -> Unit
typealias Predicate<T> = (T) -> Boolean
fun netStatus(): Int = 0
class Network {
inner class Node
}
fun <T> enable(t: T, p: Predicate<T>): Boolean {
return p(t)
}