Kotlin 快速入門

前言

人生苦多,快來 Kotlin ,快速學習Kotlin!html

什麼是Kotlin?

Kotlin 是種靜態類型編程語言 用於現代多平臺應用 100%可與Java™和Android™互操做,它是[JetBrains]開發的基於JVM的語言 開發IDE : Intellij / AndroidStudio3.0 previewjava

參考: Kotlin 官網 / Kotlin 語言中文站node

Example

Github KotlinDemo Hexo Siteandroid

源文件與包

Kotlin 源文件以 kt 結尾. 源文件全部內容(不管是類仍是函數)都包含在聲明的包內. NOTE: 源文件一般以包聲明開頭, 沒有指明包,則該文件的內容屬於無名字的默認包(屬於root package)。git

package demo 
複製代碼

NOTE: 若聲明的包路徑與文件路徑不一致,亦能夠正常編譯. 不過會有如Java 同樣的警告 Package directive doesn't match file locationgithub

默認導入

Kotlin 如同Java 同樣 默認源文件會導入如下包:web

kotlin.*
kotlin.annotation.*
kotlin.collections.*
kotlin.comparisons.* (自 1.1 起)
kotlin.io.*
kotlin.ranges.*
kotlin.sequences.*
kotlin.text.*
//根據目標平臺還會導入額外的包:
JVM:
java.lang.*
kotlin.jvm.*
JS:
kotlin.js.*
複製代碼

import

導入語法express

import (used by preamble)
  : "import" SimpleName{"."} ("." "*" | "as" SimpleName)? SEMI?
  ;`
複製代碼

Java vs Kotlin 1.若是出現名字衝突,Kotlin 可使用 as 關鍵字在本地重命名衝突項來消歧義 2.Kotlin 的關鍵字 import 不只僅限於導入類,還能夠導入頂層函數及屬性,在對象聲明中聲明的函數和屬性,枚舉常量等.編程

NOTE: 與 Java 不一樣,Kotlin 沒有單獨的 import static 語法; 全部這些聲明都用 import 關鍵字導入。小程序

頂層聲明

1.同 package 下的Kotlin 源文件,在頂層所聲明的常量,變量以及函數等不容許重複定義,不然報Conflicting 錯誤。

2.若聲明用 private 可見性修飾符修飾時,屬於當前文件私有。

基本數據類型

Numbers

Kotlin 一切都是對象。Kotlin提供如下表明數字、字符的內置類型(這很接近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的數字。以下

val c:Char='c'
val i: Int = c.toInt()
println(c) // 'c'
println(i) // 99
複製代碼

字面常量

Kotlin 惟獨不支持八進制

  • 十進制: 123
  • 二進制: 0b00001011
  • 十六進制: 0x0F

Kotlin 數值表示方法

  • 默認 Double:123.5123.5e10
  • Float 用 f 或者 F 標記: 123.5f
  • Long 用大寫 L 標記: 123L

NOTE:支持數字字面值中的下劃線(自 kotlin1.1 起)

val oneMillion = 1_000_000
複製代碼

Kotlin 裝箱機制

Kotlin 內置類型在 Java 平臺上是存儲爲 JVM 的原生類型,但一個可空的引用(如 Int?)或泛型狀況下(如 Array<Int>,List<Int> ...) 會把數字和字符自動裝箱成相應包裝類, 請參考 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 //對應 java.lang.Integer 一個裝箱的 Int
val anIntegerB: Int? = 123 //對應 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:一個可空的引用(如 Int?)能不能裝換成 Int ,答案是確定的。強制轉換或者 !!

val anIntegerA: Int? = 123 
 val anNewIntA: Int = anIntegerA  //編譯錯誤
 val anNewIntB: Int = anIntegerA!!  //或者 anIntegerA as Int
 val anNewIntC: Int = anIntegerA //Start cast to kotlin.Int

複製代碼

顯式轉換

每一個數字類型支持以下的轉換:

  • toByte(): Byte
  • toShort(): Short
  • toInt(): Int
  • toLong(): Long
  • toFloat(): Float
  • toDouble(): Double
  • toChar(): Char

以下示例 Java 中,包裝類不能隱式轉換, Kotlin 也是如此, 不一樣類型之間是不能相互隱式轉換的。

Byte a  = 1;
Integer b = a;//error
複製代碼
val b: Byte = 1 // OK, 字面值是靜態檢測的
val i: Int = b // 錯誤
複製代碼

運算

Kotlin支持數字運算的標準集,運算被定義爲相應的類成員(但編譯器會將函數調用優化爲相應的指令)。 參見操做符重載

對於位運算,沒有特殊字符來表示,而只可用中綴方式調用命名函數,例如:

val x = (1 shl 2) and 0x000FF000
複製代碼

這是完整的位運算列表(只用於 IntLong):

  • shl(bits) – 有符號左移 (Java 的 <<)
  • shr(bits) – 有符號右移 (Java 的 >>)
  • ushr(bits) – 無符號右移 (Java 的 >>>)
  • and(bits) – 位與
  • or(bits) – 位或
  • xor(bits) – 位異或
  • inv() – 位非

字符串

字符串用 String 類型表示。字符串是不可變的。 字符串的元素——字符可使用索引運算符訪問: s[i]。 能夠用 for 循環迭代字符串:

val s = "Hello, world!\n" //字符串字面值
//字符串
for (c in s) {
    print(c)
}
//原生字符串 使用三個引號(""")分界符括起來
val text = """ for (c in s) { print(c) } """
println(text)
//字符串模板
val str = "$s.length is ${s.length}"
println(str)
複製代碼

NOTE: 模板表達式以美圓符($)開頭,若要對象屬性時要花括號括起來,若要表示字面值 $ 字符z則:

val price = "${'$'}9.99"
println(price)
複製代碼

數組

數組在 Kotlin 中使用 Array 類來表示,它定義了 getset 函數(按照運算符重載約定這會轉變爲 [])和 size 屬性,以及一些其餘有用的成員函數:

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.ktarrayOf() arrayOfNulls 函數以及Array構造函數能建立數數組:

val args: Array<Int> = arrayOf(1, 2, 3)
val arrayOfNulls = arrayOfNulls<Int>(10) //空數組
val initArray = Array(5, { i -> (i * i).toString() }) //構造函數init
println(arrayOfNulls.size)
複製代碼

NOTE: 與 Java 不一樣的是,Kotlin 中數組是不型變的(invariant)。這意味着 Kotlin 不讓咱們把 Array<String>賦值給 Array<Any>,以防止可能的運行時失敗(可是你可使用 Array<out Any>, 參見類型投影)。

以前所說的在泛型狀況下Kotlin 會把數字和字符自動裝箱成相應包裝類, Arrays.kt 中有如下

ByteArray CharArray ShortArray IntArray LongArray FloatArray DoubleArray BooleanArray

無裝箱開銷的專門的類來表示原生類型數組, 和 Array 並無繼承關係,可是它們有一樣的方法屬性集。它們也都有相應的工廠方法。

val x: IntArray = intArrayOf(1, 2, 3)
 x[0] = x[1] + x[2]
複製代碼

數組迭代經過 iterator() 函數返回 Iterator<T> 對象進行迭代:

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()
 //下標索引
 args.forEachIndexed { index, i -> println("$index = $i") }
複製代碼

NOTE: forEach forEachIndexed 這些是Array 的擴展函數, 背後實現也是 [for 循環 ](#For 循環)

區間

區間表達式由具備操做符形式 ..rangeTodownTo 函數輔以 in!in 造成。 區間是爲任何可比較類型(Comparable<in T>)定義的,但對於整型原生類型(Int ,Long,Char),Ranges.kt 中實現了經常使用的整型區間(IntRangeLongRangeCharRange),而在 Primitives.kt中的 Int ,Long,Char 類實現了rangeTo 函數。如下是使用區間的一些示例

println((1.rangeTo(3)).contains(1)) //使用區間rangeTo函數
println(1 in (1..3)) //使用區間操做符
複製代碼

.. 建立一個區間, 實際是調用 rangeTo 函數返回原生類型 *Range 對象, in 則調用 contains函數。in *Range 還能夠用在迭代(for-循環)中。

for (index in 1..4) print(index)
複製代碼

NOTE:rangeTo 建立的區間, 範圍值是小到大, downTo 反之。他們默認 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默認爲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)
複製代碼

背後實現原理

區間實現了該庫中的一個公共接口:ClosedRange<T>

ClosedRange<T> 在數學意義上表示一個閉區間,它是爲可比較類型定義的。 它有兩個端點:startendInclusive 他們都包含在區間內。 其主要操做是 contains,一般以 in/!in 操做符形式使用。

整型數列(IntProgressionLongProgressionCharProgression)表示等差數列。 數列由 first 元素、last 元素和非零的 step 定義。 第一個元素是 first,後續元素是前一個元素加上 steplast 元素總會被迭代命中,除非該數列是空的。

數列是 Iterable<N> 的子類型,其中 N 分別爲 IntLong 或者 Char,因此它可用於 for-循環以及像 mapfilter 等函數中。 對 Progression 迭代至關於 Java/JavaScript 的基於索引的 for-循環:

for (int i = first; i != last; i += step) {
  // ……
}

複製代碼

對於整型類型,.. 操做符建立一個同時實現 ClosedRange<T>*Progression 的對象。 例如,IntRange 實現了 ClosedRange<Int> 並擴展自 IntProgression,所以爲 IntProgression 定義的全部操做也可用於 IntRangedownTo()step() 函數的結果老是一個 *Progression

數列由在其伴生對象中定義的 fromClosedRange 函數構造:

IntProgression.fromClosedRange(start, end, step)

複製代碼

數列的 last 元素這樣計算:對於正的 step 找到不大於 end 值的最大值、或者對於負的 step 找到不小於 end 值的最小值,使得 (last - first) % increment == 0

一些實用函數

  • rangeTo()
  • downTo()
  • reversed()
  • step()

程序結構

變量

分別使用 var ,val 聲明可變和不可變的變量.例子以下

val s = "Example" // A Immutable String
var v = "Example" // A Mutable String
複製代碼

聲明可變變量語法

var <propertyName>[: <PropertyType>] [= <property_initializer>]
複製代碼

聲明不可變變量(僅賦值一次只讀變量)語法

var <propertyName>[: <PropertyType>] = <property_initializer>
複製代碼

默認 Kotlin 變量類型是能經過賦值時智能推斷該變量的類型,且該var變量只能該類型的的值。顯式肯定變量類型,必需要接收該類型的初始化。經過一個簡單例子說明

val aImmutableIntVariable = 0x001 //aImmutableIntVariable 類型爲 Int
var aMutableIntVariable: Int = "0x002" //語法error
var bMutableIntVariable: Int = 0x002
var cMutableVariable: Any //顯式肯定變量類型,必需要接收該類型的初始化。

aImmutableIntVariable = 1 //不能從新分配 Val cannot be reassigned
bMutableIntVariable = ""//一旦類型肯定,只能接受該類型的值
複製代碼

NOTE: var 變量直接賦值爲 null ,該變量則不符合預期的類型 簡單來講(Nothing),再次賦值時報錯。

var aNullable = null
aNullable = 1;//Nothing
複製代碼

更詳細的類型介紹:類型安全和智能轉換

常量 (編譯期)

已知值的屬性可使用 const 修飾符標記爲 編譯期常量.必須知足如下需求

  1. 位於頂層或者是 object 的一個成員
  2. String 或原生類型 值初始化
  3. 沒有自定義 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 中類不能有字段。然而,當使用自定義訪問器時,有時有一個幕後字段(backing field)有時是必要的。爲此 Kotlin 提供一個自動幕後字段,它可經過使用 field 標識符訪問。

var counter = 0 // 此初始器值直接寫入到幕後字段
set(value) {
  if (value >= 0)
  field = value
}
複製代碼

field 標識符只能用在屬性的訪問器內。

若是屬性至少一個訪問器使用默認實現,或者自定義訪問器經過 field 引用幕後字段,將會爲該屬性生成一個幕後字段。

例如,下面的狀況下, 就沒有幕後字段:

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() // 類型參數已推斷出
        }
        return _table ?: throw AssertionError("Set to null by another thread")
    }
複製代碼

從各方面看,這正是與 Java 相同的方式。由於經過默認 getter 和 setter 訪問私有屬性會被優化,因此不會引入函數調用開銷。

控制流:if、when、for、while

if 語句、if - else 表達式

在 Kotlin 中,沒有Java中三元運算符(條件 ? 而後 : 不然), 由於if - else 是一個表達式,即它會返回一個值。

val num1 = 1
val num2 = 2
val max = if (num1 > num2) num1 else num2
println(max)
println(if (num1 < num2) "if - else 表達式" else num2)
複製代碼

if的分支能夠是代碼塊,最後的表達式做爲該塊的值:

println(
        if (num1 < num2) {
            println("num1 < num2")
            "if - else 表達式"
        } else num2
)
複製代碼

When 表達式

在 Kotlin 中,when 取代了Java中 switch 。聲明語法以下:

when[(表達式)]{
  [條件分支1,條件分支2(可多個 逗號分隔)] -> controlStructureBody [SEMI]
  [else 分支] -> controlStructureBody [SEMI]
}
複製代碼

SEMI 表明 ;或者 換行 , 在controlStructureBody 是代碼塊且有變量聲明時使用, 示例:

when {
} //最簡單的形式

val randomNum = Random().nextInt(5)
when (randomNum) {
  0, 1 -> println("randomNum == 0 or randomNum == 1") //多個分支條件放在一塊兒,用逗號分隔
  2 -> println("randomNum == 2")
  else -> println("otherwise")
}

//若是不提供參數,全部的分支條件都是簡單的布爾表達式,而當一個分支的條件爲真時則執行該分支
when {
  randomNum == 0 -> {
    var a = 2; println("is 0") //;
    var b = 3
    println("is 0") //換行
  }
}
//其餘分支都不知足條件將會求值 else 分支
when {
  randomNum == 0 -> println("randomNum is 0")
  randomNum == 1 -> println("randomNum is 1")
  else -> println("otherwise")
}
複製代碼

NOTE: when 做爲一個表達式使用,則必須有 else 分支, 除非全部的可能狀況都已經覆蓋了。

val an = when (1) {
    1 -> 1
    else -> "never arrive"
}
println(an)
when (randomNum == 3) {
    true -> println("is 3")
    false -> println(randomNum)
}
複製代碼

For 循環

for 循環能夠對任何提供迭代器(iterator)的對象進行遍歷。語法

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() //索引遍歷一個數組或者一個 list
for (item in array) print(item);println()
//庫函數 forEachIndexed
array.forEachIndexed { index, item ->  print("[$index] = $item \t")}
println()
//庫函數 withIndex
for ((index, value) in array.withIndex()) {
    println("the element at $index is $value")
}
複製代碼

如上所述,for 能夠循環遍歷任何提供了迭代器的對象。即:

  • 有一個成員函數或者擴展函數 iterator(),它的返回類型 Iterator<T>
  • 有一個成員函數或者擴展函數 next()
  • 有一個成員函數或者擴展函數 hasNext() 返回 Boolean

這三個函數都須要標記爲 operator

While 循環

whiledo..while 照常使用。小白應該也能夠搞掂吧。。。

循環中的Break和continue

在循環中 Kotlin 支持傳統的 breakcontinue 操做符。

返回和跳轉

Kotlin 有三種結構化跳轉表達式:

  • return。默認從最直接包圍它的函數或者匿名函數返回。
  • break。終止最直接包圍它的循環。
  • continue。繼續下一次最直接包圍它的循環。

標籤

在 Kotlin 中任何表達式均可以用標籤(label)來標記。 標籤的格式爲標識符後跟 @ 符號,例如:abc@fooBar@都是有效的標籤(參見語法)。 要爲一個表達式加標籤,咱們只要在其前加標籤便可。

Break 和 Continue 的標籤控制跳轉, return 標籤控制返回目標,示例:

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())
複製代碼

面向對象

類與對象

聲明類(class)語法

[訪問修飾符 默認public] [非訪問修飾符 默認 final] class 類名 
	[訪問修飾符 默認public] [主構造函數] [參數] [: 父類 默認爲 Any]  [類體]
複製代碼

定義類,咱們經過下面的例子來講明:

class EmptyClass 
println(EmptyClass() is Any)
複製代碼

NOTE: [] 表明能夠省略. Kotliin 中修飾符 與Java 略不一樣,Java語言提供了不少修飾符,主要分爲如下兩類:

  • 訪問修飾符
  • 非訪問修飾符

更詳細的 Java 修飾符 請參考 Java 修飾符 _ 菜鳥教程

Kotliin 中沒顯式聲明修飾符 ,默承認見性是 public

訪問控制修飾符

類、對象、接口、構造函數、方法、屬性和它們的 setter 均可以有 可見性修飾符。 (getter 老是與屬性有着相同的可見性。) 在 Kotlin 中有這四個可見性修飾符:privateprotectedinternalpublic

修飾符 是否支持頂級聲明 當前文件 同一模塊
private Y Y N
protected N ~~~~ ~~~~
internal Y Y Y
public Y Y Y

NOTE:

  1. protected 不支持頂級聲明,由於文件沒有繼承關係。

  2. internal 是編譯在一塊兒的一套 Kotlin 文件:

    • 一個 IntelliJ IDEA 模塊;
    • 一個 Maven 項目;
    • 一個 Gradle 源集;
    • 一次 <kotlinc> Ant 任務執行所編譯的一套文件。
  3. 對於類和接口內部聲明的成員可見修飾符與Java 相似:

    • private 僅該類和接口內部可見;
    • protectd 該類和接口內部可見且子類可見
    • internal 該模塊內 可見
    • public 均可見

非訪問控制修飾符

kotlin 定義類、對象、構造函數、方法、屬性時默認加了 final 修飾符, 接口默認是 open 與之相反。能被繼承、被覆蓋。

NOTE:在 final 修飾 class 下 用 open 修飾該類的成員無效,在 final 缺省修飾符下 再用 final 修飾顯得 Redundant 冗餘,但在 override 時可以使用final 關鍵字再度修飾

咱們經過下面的例子來講明:

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 中外部類不能訪問內部類的 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:局部變量、函數和類不能有可見性修飾符。Kotlin 中外部類不能訪問內部類的 private 成員(與Java不一樣)。

類成員

類能夠包含

構造函數

一個類能夠有一個主構造函數和一個或多個次構造函數。主構造函數是類頭的一部分:它跟在類名(和訪問修飾符 [默認 public])後。主構造函數有註解或可見性修飾符,這個 constructor 關鍵字是必需的,而且這些修飾符在它前面。非抽象類沒有聲明任何(主或次)構造函數,它會有一個生成的不帶參數的主構造函數。構造函數的可見性是 public。

NOTE:若要修改主構造函數的可見性,須要添加一個顯式 constructor 關鍵字

class A private constructor() { …… }
複製代碼

Kotlin 十分簡便, 能夠在主構造函數內聲明屬性(可變的(var)或只讀的(val))以及初始化屬性默認值(次構造函數是不容許的), 且爲該類成員屬性, 主構造函數內不能包含除了聲明屬性任何的代碼。提供了 init 關鍵字做爲前綴的初始化塊(initializer blocks)

次構造函數

聲明在類體內以 constructor 關鍵字的函數。若該類有主構造函數,次構造函數都須要用 this 關鍵字直接或間接委託給主構造函數。

open class Person /*private*/ constructor(firstName: String) {
    class A //empty class 下面接着是次構造函數 ,Error: Expecting member declaration, 期待成員聲明

    val money = 1000_000

    init {
        println("init block: firstName= $firstName")
        println("init block: money= $money")
    }

    //次構造函數
    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 上,若是主構造函數的全部的參數都有默認值,編譯器會生成 一個額外的無參構造函數,它將使用默認值。這使得 Kotlin 更易於使用像 Jackson 或者 JPA 這樣的經過無參構造函數建立類的實例的庫。

class Customer(val customerName: String = "")
複製代碼

建立類的實例

Kotlin 並不須要 new 關鍵字建立實例, 像普通函數同樣調用構造函數便可。

繼承

Java 的超類是 Object , 而 Kotlin 的是 Any。

若父類有主構造函數且帶參數,子類必須用主構造函數將參數初始化,以下:

class Student(firstName: String) : Person(firstName) {
}
複製代碼

注意:參數初始化時,子父類必須一致。

父類沒有主構造函數, 那麼每一個次構造函數必須使用 super 關鍵字初始化其基類型。

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)
}

//容許經過主構造函數覆蓋次構造函數
class Man(name: String) : Human(name)
複製代碼

覆蓋(override)

final , open 是否可覆蓋修飾符 和 override 標註覆蓋類、對象、接口、構造函數、方法、屬性。

覆蓋規則

在 Kotlin 中,實現繼承由下述規則規定:若是一個類從它的直接超類繼承相同成員的多個實現, 它必須覆蓋這個成員並提供其本身的實現(也許用繼承來的其中之一)來消除歧義。 爲了表示採用 從哪一個超類型繼承的實現,咱們使用由尖括號中超類型名限定的 super ,如 super :

open class Thread {
    open fun run() {
        println("Thread#run")
    }

    fun start() {
        println("Thread#start")
    }
}

interface Runnable {
    fun run() {
        println("Thread#run")
    } // 接口成員默認就是「open」的


}

class HandlerThread() : Runnable, Thread() {
    //編譯器要求覆蓋 run():
    override fun run() {
        super<Thread>.run() // 調用 Thread.run()
        super<Runnable>.run() // 調用 Runnable.run()
    }
}
複製代碼

抽象類

類和其中的某些成員能夠聲明爲 abstract 。 抽象成員在本類中能夠不用實現。 須要注意的是,咱們並不須要用 open 標註一個抽象類或者函數——由於這不言而喻。

abstract class AbstractClass{ //open 多餘的,由於抽象類終究是父類,因此更不能用final 修飾
    open fun doSomething() {
    }

    abstract fun fly() //子類必須 override
}

class AbstractClassImpl : AbstractClass() {
    override fun fly() {
    }

    override fun doSomething() {//override 開放成員
        super.doSomething()
    }
}
複製代碼

接口

用關鍵字 interface 來定義接口。Kotlin 的接口函數能夠有實現, 屬性必須是抽象的(默認抽象), 或者提供 get 訪問器實現, 且不能有幕後字段(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
    }
}
複製代碼

嵌套類和內部類

類能夠嵌套在其餘類中

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
    }

    //內部類 標記爲 inner 以便可以訪問外部類的成員。內部類會帶有一個對外部類的對象的引用
    inner class KotlinInnerClass {
        fun bra() = bra
        fun reference() = this@KotlinNestedInnerClass  //This 表達式
    }

    //匿名內部類 @see 對象聲明(object)
  
}
複製代碼

若是對象是函數式 Java 接口(即具備單個抽象方法的 Java 接口)的實例, 你可使用帶接口類型前綴的lambda表達式建立它:

val run  = Runnable {  }
複製代碼

對象(object)

在Java 中, 匿名內部類隨處可見。然而 Kotlin 用 object 關鍵字提供了對象聲明以及對象表達式特性, 建立單例、匿名對象, 伴生對象(類內部的對象聲明) so easy。

val point = object /*: Any()*/ { //默認繼承 Any
    var x: Int = 0 //必須進行初始化
    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)

//對象聲明
object Singleton { //決不能聲明局部做用域(函數中)
}
複製代碼

NOTE: 如何區分對象聲明和對象表達式, 顧名思義, 有名字的是對象聲明(object Singleton), 沒名字的是對象表達式(anonymous object)。

關於 object 使用細節,下面經過一個簡單例子爲你們演示:

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) //錯誤:未能解析的引用「name」
        var visible = true
        call(object : CallBack {
            override fun call() {
                visible //對象表達式中的代碼能夠訪問來自包含它的做用域的變量
                println("Anonymous#call@${this.hashCode()}")
            }

        })
        call (object : CallBack {
            override fun call() {
                visible //對象表達式中的代碼能夠訪問來自包含它的做用域的變量
                println("Anonymous#call@${this.hashCode()}")
            }

        })
        call(OneCallBack)
        call(OneCallBack)

    }

    object OneCallBack : CallBack {
        //由於對象表達式不能綁定名字,這稱爲對象聲明
        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()
}

複製代碼

私有函數時,返回object類型是匿名對象類型, 不然是 Any。與Java 不一樣內部類也可訪問非 final 變量。對象聲明實則是單例。

伴生對象(companion object)

與 Java 或 C# 不一樣,在 Kotlin 中類沒有靜態方法。在大多數狀況下,它建議簡單地使用包級函數。

類內部的對象聲明能夠用 companion 關鍵字標記:

open class World {

    //Companion 是companion object 默認名字可省略,僅且有一個伴生對象
    companion object Companion : Observer {
        @JvmField //@JvmField 標註這樣的屬性使其成爲與屬性自己具備相同可見性的靜態字段。
        val time = System.nanoTime()

        const val VERSION = "1.1.4.2" //kotlin 常量(const 標註的(在類中以及在頂層的)屬性), 在 Java 中會成爲靜態字段:

        override fun update(o: Observable?, arg: Any?) {
        }


        // @JvmStatic //打開註釋編譯報錯,存在相同的函數聲明, 這充分地證實了伴生對象的成員看起來像其餘語言的靜態成員,在運行時他們仍然是真實對象的實例成員
        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 中調用

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:伴生對象實際是對象的實例成員, JVM 平臺,若是使用 @JvmStatic 註解,你能夠將伴生對象的成員生成爲真正的靜態方法和字段。更詳細信息請參見Java 互操做性一節 。

對象表達式和對象聲明之間有一個重要的語義差異:

  • 對象表達式是在使用他們的地方當即執行(及初始化)的
  • 對象聲明是在第一次被訪問到時延遲初始化的
  • 伴生對象的初始化是在相應的類被加載(解析)時,與 Java 靜態初始化器的語義相匹配

數據類

咱們常常建立一些只保存數據的類。在這些類中,一些標準函數每每是從數據機械推導而來的。在 Kotlin 中,這叫作 數據類 並標記爲 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)

    //數據類和解構聲明
    val (name, age) = KotlinDataClass.User("Lisa", 18)
    println("$name, $age years of age")

    //標準數據類
    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

    //數據類自己是 final,必須有主構造器,至少一個參數
    data class User(val name: String, val age: Int = 0) : Person() {

        //編譯器會根據主構造函數的參數生成如下函數,根據需求 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(){
//
// }
    }

}
複製代碼

編譯器自動從主構造函數中聲明的全部屬性導出如下成員:

  • equals()/hashCode() 對,
  • toString() 格式是 "User(name=John, age=42)"
  • componentN() 函數 按聲明順序對應於全部屬性,
  • copy() 函數, 複製一個對象僅改變某些屬性。

爲了確保生成的代碼的一致性和有意義的行爲,數據類必須知足如下要求:

  • 主構造函數須要至少有一個參數;
  • 主構造函數的全部參數須要標記爲 valvar
  • 數據類不能是抽象、開放、密封或者內部的;
  • (在1.1以前)數據類只能實現接口。

自 1.1 起,數據類能夠擴展其餘類(示例請參見密封類)。

在 JVM 中,若是生成的類須要含有一個無參的構造函數,則全部的屬性必須指定默認值。 (參見構造函數)。

密封類

密封類用來表示受限的類繼承結構:當一個值爲有限集中的類型、而不能有任何其餘類型時。在某種意義上,他們是枚舉類的擴展:枚舉類型的值集合也是受限的,但每一個枚舉常量只存在一個實例,而密封類的一個子類能夠有可包含狀態的多個實例。

NOTE: sealed 不能修飾 interface ,abstract class(會報 warning,可是不會出現編譯錯誤)

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 分支 已經覆蓋了全部的狀況
}
複製代碼

枚舉類

枚舉類的最基本的用法是實現類型安全的枚舉, 每一個枚舉常量都是一個對象, 需用逗號分開。示例以下

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;
    }

    //枚舉都是枚舉類的實例,能夠初始化
    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))
    }

}
複製代碼

枚舉常量還實現了 Comparable 接口, 其中天然順序是它們在枚舉類中定義的順序。

NOTE: val (name, ordinal) = KotlinEnumClass.Direction.EAST 之因此能夠編譯經過, 由於我對枚舉類進行解構聲明

//學而致用
operator fun <E : Enum<E>> Enum<E>.component1() = this.name
operator fun <E : Enum<E>> Enum<E>.component2() = this.ordinal
複製代碼

註解類

學習Java 的應該對註解不陌生,不瞭解能夠先看看 Java的註解

註解聲明

[訪問修飾符 默認public] [非訪問修飾符 默認只能爲 final 不能顯式修飾] annotation class 類名 
	[訪問修飾符 只能爲public] [主構造函數 constructor 關鍵字無關緊要] [val參數] 
複製代碼
internal annotation class KotlinFileName(val name:String)
複製代碼

容許的參數類型有:

  • 對應於 Java 原生類型的類型(Int、 Long等)以及字符串
  • KClass、枚舉
  • 其餘註解
  • 上面已列類型的數組

NOTE: 註解參數不能有可空類型,由於 JVM 不支持將 null 做爲註解屬性的值存儲。若是註解用做另外一個註解的參數,則其名稱不以 @ 字符爲前綴, 且新的註解類訪問權限不能比其中一個註解的參數的訪問權限要大

internal annotation class FileScope 
		constructor(@ApplicationScope val file: KotlinFileName)
複製代碼

註解的附加屬性能夠經過用元註解標註註解類來指定:

  • @Target 指定能夠用該註解標註的元素的可能的類型(類、函數、屬性、表達式等);
  • @Retention 指定該註解是否存儲在編譯後的 class 文件中,以及它在運行時可否經過反射可見 (默認都是 true);
  • @Repeatable 容許在單個元素上屢次使用相同的該註解;
  • @MustBeDocumented 指定該註解是公有 API 的一部分,而且應該包含在生成的 API 文檔中顯示的類或方法的簽名中。
@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
annotation private class ApplicationScope
複製代碼

Lambda 表達式

註解也能夠用於 lambda 表達式。它們會被應用於生成 lambda 表達式體的 invoke() 方法上。

annotation class Anonymous
val run = @KotlinAnnotation.Anonymous { println("run") }

複製代碼

Use-site Targets (使用處 目標)

當對屬性或主構造函數參數進行標註時,從相應的 Kotlin 元素生成的 Java 元素會有多個,所以在生成的 Java 字節碼中該註解有多個可能位置 。支持的使用處目標的完整列表爲:

  • file
  • property(具備此目標的註解對 Java 不可見)
  • field
  • get(屬性 getter)
  • set(屬性 setter)
  • receiver(擴展函數或屬性的接收者參數)
  • param(構造函數參數)
  • setparam(屬性 setter 參數)
  • delegate(爲委託屬性存儲其委託實例的字段)

可使用相同的語法來註釋整個文件。要執行此操做,請將目標文件的註釋放在文件的頂層,在包指令以前或在全部導入以前,若是文件位於默認包中:

@file:JvmName("KotlinAnnotationKt")

package demo
複製代碼

若是要指定精確地指定應該如何生成該註解,請使用如下語法:

@處目標元素:[註解A 註解B ] ... //同一目標只有1個註解時方括號能夠省略
複製代碼

簡單示例以下:

class User(@field:FieldScope val name: String, @get:[ApplicationScope FunScope] val age: Int)
複製代碼

若是不指定使用處目標,則根據正在使用的註解的 @Target 註解來選擇目標 。

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); // !!! ClassCastException:沒法將整數轉換爲字符串
複製代碼

PECS原則,在Java <? extends T>、<? super T> 通配符類型參數,前者只能讀取, 不能寫入,後者反之。便有一條規律,」Producer Extends, Consumer Super」:

  • Producer Extends – 若是你須要一個只讀List,用它來produce T,那麼使用? extends T
  • Consumer Super – 若是你須要一個只寫List,用它來consume T,那麼使用? super T
  • 若是須要同時讀取以及寫入,那麼咱們就不能使用通配符了。

一樣PECS原則適用於 Kotlin:

  • Producer Extends – 使得類型是協變的(covariant)
  • Consumer Super – 使得類型是逆變性(contravariance)

NOTE: *PECS 表明生產者-Extens,消費者-Super(Producer-Extends, Consumer-Super)。*一個生產者對象,只是保證類型安全

聲明處型變

Java 中List<String> 不能直接賦值List<Object> ,在 Kotlin 中,提供 out 修飾符確保接口或類成員中返回out(生產),並從不被 in (消費)。

val stringList = listOf<String>()
val anyList: List<Any> = stringList
複製代碼

kotlin List 接口聲明:

public interface List<out E> : Collection<E> 
複製代碼

in。它使得一個類型參數逆變:只能夠被消費而不能夠被生產。逆變類的一個很好的例子是 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!
}
複製代碼

類型參數 T 被聲明爲 out 時,雖然 **<Base> 能夠安全地做爲 **<Derived>的超類, 就只能出現輸出-位置。

由於它在類型參數聲明處提供,因此被稱作聲明處型變。 這與 Java 的使用處型變相反,其類型用途通配符使得類型協變。in 反之。

**NOTE:消費者 in, 生產者 out **

類型投影 (使用處型變)

將類型參數 T 聲明爲 out 很是方便,而且能避免使用處子類型化的麻煩,可是有些類實際上不能限制爲只返回 T 好比 Array:

val ints: Array<out Int> = arrayOf(1, 2, 3)
val any = Array<Any>(3) { "" }
//out 生產者 至關於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 消費者 至關於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

星投影

若類型參數一無所知,但仍然但願以安全的方式使用它。 這裏的安全方式是定義泛型類型的這種投影,該泛型類型的每一個具體實例化將是該投影的子類型。

Kotlin 爲此提供了所謂的星投影語法:

val star: List<*> = listOf("C", "D", 1, 2)
val any1: Any? = star[0]
fun compareTo2(x: Comparable<*>) 
複製代碼
  • 對於 Foo <out T>,其中 T 是一個具備上界 TUpper 的協變類型參數,Foo <*> 等價於 Foo <out TUpper>。 這意味着當 T 未知時,你能夠安全地從 Foo <*> 讀取 TUpper 的值。
  • 對於 Foo <in T>,其中 T 是一個逆變類型參數,Foo <*> 等價於 Foo <in Nothing>。 這意味着當 T 未知時,沒有什麼能夠以安全的方式寫入 Foo <*>
  • 對於 Foo <T>,其中 T 是一個具備上界 TUpper 的不型變類型參數,Foo<*> 對於讀取值時等價於 Foo<out TUpper> 而對於寫值時等價於 Foo<in Nothing>

若是泛型類型具備多個類型參數,則每一個類型參數均可以單獨投影。 例如,若是類型被聲明爲 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 的原始類型,可是安全。

泛型約束

可以替換給定類型參數的全部可能類型的集合能夠由泛型約束限制。

最多見的約束類型是與 Java 的 extends 關鍵字對應的 上界

fun <T : Number> add(t: T) {
    // ……
}

add(1)
add("") //not allow
複製代碼

默認的上界(若是沒有聲明)是 Any?。在尖括號中只能指定一個上界。 若是同一類型參數須要多個上界,咱們須要一個單獨的 where-子句:

fun <T> cloneWhenGreater(t: T)
        where T : Number,
              // T : String, 只指定一個class ,接口能夠多個
              T : kotlin.Comparable<T>,
              T : Cloneable {
}
複製代碼

擴展

Kotlin 同 C# 和 Gosu 相似,可以擴展一個類的新功能而無需繼承該類或使用像裝飾者這樣的任何類型的設計模式。 這經過叫作 擴展 的特殊聲明完成。Kotlin 支持 擴展函數擴展屬性

擴展函數和屬性

聲明一個擴展函數和屬性,咱們須要用一個 接收者類型 也就是被擴展的類型來做爲他的前綴。

class KotlinExtension {
    //成員函數比擴展函數優先
    fun member() {
        println("call#member")
    }

    fun fileName(): String {
        return "KotlinExtension.class"
    }

    companion object
}

//擴展的對象類型 KotlinExtension
fun KotlinExtension.extensionFun() {
    println("this@${this} call#extensionFun") //
}

fun KotlinExtension.member() {
    println("call#extension") //
}

//接收者類型表達式中使用泛型 要在函數名前聲明泛型參數
fun <E> List<E>.addAll(){
    //...
}

//擴展屬性(Extension Property) 實際擴展get* 函數而已
val KotlinExtension.fileName
    get() = "KotlinExtension.kt"
複製代碼

NOTE: this 關鍵字在擴展函數內部對應到接收者對象(傳過來的在點符號前的對象)

可空接收者

可空的接收者類型也能定義擴展,在對象變量上調用值爲 null時,而且能夠在函數體內檢測 this == null

檢測發生在擴展函數的內部。最好的例子,如 Library.kt中:

public fun Any?.toString(): String
複製代碼

伴生對象的擴展

伴生對象的擴展和定義擴展函數和屬性一致:

val KotlinExtension.Companion.anProperty: Int get() = 1
fun KotlinExtension.Companion.extensionFun() {
    println("call#Companion.extensionFun")
}
複製代碼

擴展的做用域

大多數在頂層定義擴展,要使用所定義包以外的一個擴展,導包就可使用它。類內部也能夠聲明擴展(我認爲這並沒有卵用)在這樣的擴展內部,該類的對象和接收者的對象成員,自由訪問。擴展聲明所在的類的實例稱爲 分發接收者,擴展方法調用所在的接收者類型的實例稱爲 擴展接收者

class KotlinInteriorExtension {
    fun start() {
        println("call#start")
    }

    fun KotlinExtension.stop(){
        start()
        member() //擴展聲明爲成員時 擴展函數優先
        this@KotlinInteriorExtension.member() //使用 限定this
    }

    fun member() {
        println("call#member")
    }
}
複製代碼

擴展是靜態解析的

謹記擴展不能真正的修改他們所擴展的類, 僅僅是能夠經過該類型的變量用點表達式去調用這個新函數。

擴展函數是靜態分發的,是由函數調用所在的表達式的類型來決定。

//擴展是靜態解析的
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 —— 分發接收者虛擬解析

LocalBookmarkManage().syncLocal(cloudBookmark)//輸出 syncToCloud —— 擴展接收者靜態解析
CloudBookmarkManage().syncLocal(localBookmark)//輸出 syncFromLocal —— 分發接收者虛擬解析
複製代碼

函數的分發對於分發接收者類型是虛擬的,但對於擴展接收者類型必定是靜態的。

委託

kotlin 支持委託類和屬性, 使用關鍵字 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 {
    //可覆蓋 , 不覆蓋轉發printer print 方法
    override fun print() {
        printer.print()
        println("override#print")
    }
}

fun main(args: Array<String>) {
    MultipurposePrinter(ColorPrinter()).print()
    MultipurposePrinter(BlackPrinter()).print()
}
複製代碼

by xxa -子句表示xxa 將會在 類中內部存儲。 而且編譯器將生成轉發給 xxa 的全部成員函數。

委託屬性

kotlin 標準庫實現以下常見的屬性類型:

  • 延遲屬性(lazy properties): 其值只在首次訪問時計算,
  • 可觀察屬性(observable properties): 監聽器會收到有關此屬性變動的通知,
  • 把多個屬性儲存在一個映射(map)中,而不是每一個存在單獨的字段中。
延遲屬性 Lazy

lazy() 是接受一個 lambda 並返回一個 Lazy <T> 實例的函數,返回的實例能夠做爲實現延遲屬性的委託: 第一次調用 get() 會執行已傳遞給 lazy() 的 lambda 表達式並記錄結果, 後續調用 get() 只是返回記錄的結果。

默認狀況下,對於 lazy 屬性的求值是同步鎖的(synchronized):該值只在一個線程中計算,而且全部線程會看到相同的值。若是初始化委託的同步鎖不是必需的,這樣多個線程能夠同時執行,那麼將 LazyThreadSafetyMode.PUBLICATION 做爲參數傳遞給 lazy() 函數。 而若是你肯定初始化將老是發生在單個線程,那麼你可使用 LazyThreadSafetyMode.NONE 模式, 它不會有任何線程安全的保證和相關的開銷。

val lazyValue by lazy<String>(LazyThreadSafetyMode.SYNCHRONIZED) {
    println("computed!")
    "Hello" //同步鎖的(synchronized)
}
println(lazyValue)
println(lazyValue)
複製代碼

這個例子輸出:

computed!
Hello
Hello
複製代碼
可觀察屬性 Observable

Delegates.observable() 接受兩個參數:初始值和修改時處理程序(handler)。 每當咱們給屬性賦值時會調用該處理程序(在賦值執行)。它有三個參數:被賦值的屬性、舊值和新值。

若是你想可以截獲一個賦值並「否決」它,就使用 vetoable() 取代 observable()。 在屬性被賦新值生效以前會調用傳遞給 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)
複製代碼

這個例子輸出:

被賦值的屬性:name,  No Name > 両儀式
被賦值的屬性:name,  両儀式 > 式
被賦值的屬性:skip,  Null > Test
Null
複製代碼
把屬性儲存在映射中

Map 可做爲委託來實現委託屬性。

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() 強大的初始化:

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 屬性須要實現 getValue() setValue() 函數,val 只是須要getValue() 便可。兩函數都須要用 operator 關鍵字來進行標記。

委託類還能夠實現包含所需 operator 方法的 ReadOnlyPropertyReadWriteProperty 接口之一。 這倆接口是在 Kotlin 標準庫中聲明的:

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)
複製代碼
背後原理

在使用委託的時, 不難發現該屬性是委託類型。好比: p is String ,輸出false。

在每一個委託屬性的實現的背後,Kotlin 編譯器都會生成輔助屬性並委託給它。 例如,對於屬性 prop,生成隱藏屬性 prop$delegate,而訪問器的代碼只是簡單地委託給這個附加屬性:

class C {
    var prop: Type by MyDelegate()
}

// 這段是由編譯器生成的相應代碼:
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 編譯器在參數中提供了關於 prop 的全部必要信息:第一個參數 this 引用到外部類 C 的實例而 this::propKProperty 類型的反射對象,該對象描述 prop 自身。

提供委託

kotlin 提供 provideDelegate 操做符,能夠擴展建立屬性實現所委託對象的邏輯。使用場景是在建立屬性時(而不只在其 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
    }
  }
}
複製代碼

提供委託, 並不複雜。經過一個函數去獲取委託而已。provideDelegate 方法隻影響輔助屬性的建立,並不會影響爲 getter 或 setter 生成的代碼。

函數

函數用法

Kotlin 中的函數使用 fun 關鍵字聲明

fun funName(參數)[: returnType(默認 Unit)] ...
複製代碼

函數參數規則

  • 函數參數使用 Pascal 表示法定義,即 name: type , 參數用逗號隔開。
  • 每一個參數必須有顯式類型, 參數還能夠有默認值,當省略相應的參數時使用默認值, 以減小重載數量。
  • 覆蓋帶有默認參數值的方法時,默認參數值省略。
  • 若是一個默認參數在一個無默認值的參數以前,那麼該默認值只能經過使用命名參數調用該函數來使用
  • 若是最後一個 lambda 表達式參數從括號外傳給函數函數調用,那麼容許默認參數不傳值
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()))
複製代碼

可變數量的參數(Varargs)

函數的參數(一般是最後一個)能夠用 vararg 修飾符標記:

fun varargFun(method: String = "varargFun", vararg s: Int) {
  s.forEach { print(it) }
}

val b = intArrayOf(6, 8)
// vararg 參數 類型是基本類型,便是 *Array 類型 不然 Array<out T>
varargFun("1", 2, 4, *b, 10)
複製代碼

**伸展(spread)**操做符(在數組前面加 *),能夠數組元素添加到vararg 變量中去

返回 Unit 的函數

若是一個函數不返回任何有用的值,它的返回類型是 UnitUnit 是一種只有一個值——Unit 的類型。這個值不須要顯式返回。Unit 就像Java 的 Void

fun printHello(name: String?): Unit {
  if (name != null)
  	println("Hello ${name}")
  else
  	println("Hi there!")
  // `return Unit` 或者 `return` 是可選的
}
複製代碼

單表達式函數

當函數返回單個表達式時,能夠省略花括號而且在 = 符號以後指定代碼體便可。當返回值類型可由編譯器推斷時,顯式聲明返回類型是可選的, 但具備塊代碼體的函數必須始終顯式指定返回類型。

fun double(x: Int) = x * 2
複製代碼

中綴表示法

Kotlin支持數字運算的標準集,正是用了中綴表示法,當函數知足如下條件就能用 infix 關鍵字標註

  • 他們是成員函數或擴展函數
  • 他們只有一個參數
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")
複製代碼

函數做用域

在 Kotlin 中函數能夠在文件頂層聲明,這意味着你不須要像一些語言如 Java、C# 或 Scala 那樣建立一個類來保存一個函數。此外除了頂層函數,Kotlin 中函數也能夠聲明在局部做用域、做爲成員函數以及擴展函數。

  • 在類或對象內部定義的函數——成員函數
  • 一個函數在另外一個函數內部——局部函數
//成員函數
fun memberFun() {
  val visited = ""
  fun partialFun() {    //局部函數
    println(visited)
  }
  partialFun()
}
複製代碼

泛型函數

函數能夠有泛型參數,經過在函數名前使用尖括號指定。

fun <T> singletonList(item: T): List<T> {
  return listOf(item)
}
複製代碼

高階函數

高階函數是將函數用做參數或返回值的函數。

//函數用做參數 () -> Unit 不帶參數並 且返回 Unit 類型值的函數
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) //函數引用
  post { println("post") }
  postDelay(1000) { println("postDelay") }
}
複製代碼

() -> Unit 被稱爲函數類型 , :: 操做符可參見函數引用, 當一個函數接受另外一個函數做爲最後一個參數,lambda 表達式參數能夠在圓括號參數列表以外傳遞。 參見 callSuffix 的語法。

Lambda 表達式與匿名函數

一個 lambda 表達式或匿名函數是一個「函數字面值」,即一個未聲明的函數, 做爲表達式傳遞。

Lambda 表達式語法

lambda 表達式老是被大括號括着,完整語法形式的參數聲明放在括號內,並有可選的類型標註, 函數體跟在一個 -> 符號以後。

println({}) //輸出: () -> kotlin.Unit
println({ "String" })//輸出: () -> kotlin.String
val string = { "String" }
println(string())//輸出: String
複製代碼

挖槽,上面的是什麼鬼。沒了解Lambda 表達式 的,固然會困惑不已。

fun explicitAnonymous(): () -> Int {
  return { -> 1 } //沒參數不能有括號() -> 也可略
}
複製代碼

這樣一來就簡單明瞭。{} 聲明瞭個匿名函數,編譯器做如下處理

local final fun <anonymous>(): Unit
複製代碼

當一個空參數的匿名函數, 如 { "String" } ,編譯器會將lambda 主體中的最後一個或多是單個)表達式會視爲返回值。如果{ "String";1 } 則輸出 () -> kotlin.Int

可選的類型標註,單表達式函數時,顯式聲明返回類型是可選的,匿名的參數類型也是可選的。非單表達式函數時,則變量名可選。

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表達式約定

  • 函數的最後一個參數是一個函數,而且你傳遞一個 lambda 表達式做爲相應的參數,你能夠在圓括號以外傳遞
  • lambda 是該調用的惟一參數,則調用中的圓括號能夠徹底省略。
  • 函數字面值只有一個參數時, 那麼它的聲明能夠省略(連同 ->),其名稱是 it
  • 未使用的變量可用下劃線取代其名稱
  • lambda 隱式返回最後一個表達式的值,能夠用限定的返回語法顯式返回
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
}
複製代碼
匿名函數

顧名思義,與常規函數相同不須要指定函數名

val sumAnonymous = fun(x: Int, y: Int) = x + y //返回類型能夠自動推斷
println(sumAnonymous(1, 3))
val sumAnonymous2 = fun(x: Int, y: Int): Int {
  return x + y
}
filter<Int>(fun(item) = item > 0) //推斷出的參數類型能夠省略. 只能在括號內傳遞
複製代碼

匿名函數和lambda 是有區別的,匿名函數參數只能在括號內傳遞。 容許將函數留在圓括號外的簡寫語法僅適用於 lambda 表達式。Lambda表達式與匿名函數之間的另外一個區別是非局部返回的行爲。一個不帶標籤的 return 語句老是在用 fun 關鍵字聲明的函數中返回。這意味着 lambda 表達式中的 return 將從包含它的函數返回,而匿名函數中的 return將從匿名函數自身返回。

閉包

Lambda 表達式或者匿名函數(以及局部函數對象表達式) 能夠訪問其 閉包 ,即在外部做用域中聲明的變量。 與 Java 不一樣的是能夠修改閉包中捕獲的變量:

var aNumber = 0
run {
  aNumber += 1
}
val add = fun() {
  aNumber += 1
}
add()
println("aNumber: $aNumber")
複製代碼
帶接收者的函數字面值

Kotlin 提供了使用指定的 接收者對象 調用函數字面值的功能。 在函數字面值的函數體中,能夠調用該接收者對象上的方法而無需任何額外的限定符。 這相似於擴展函數,它容許你在函數體內訪問接收者對象的成員。 其用法的最重要的示例之一是類型安全的 Groovy-風格構建器

val sumR = fun Int.(other: Int): Int = this + other //val sumR: Int.(Int) → Int
println(1.sumR(2))
複製代碼

內聯函數

要知道使用高階函數時,每個函數都是一個對象,且會捕獲一個閉包。 因此帶來一些運行時的效率損失,即那些在函數體內會訪問到的變量。 內存分配(對於函數對象和類)和虛擬調用會引入運行時間開銷。

kotlin 支持 inline 修飾具備lambda參數的函數,以消除這類的開銷。(僅支持頂層、成員函數,即不支持局函數)

inline fun <T> lockInline(lock: Lock, body: () -> T): T {
  lock.lock()
  try {
    return body()
  } finally {
    lock.unlock()
  }
}
複製代碼

內聯原理實際上是編譯器拷貝代碼副本(如:body () -> T),這可能致使生成的代碼增長,但在循環中的「超多態(megamorphic)」 狀況下,將在性能上有所提高。

不具備lambda參數的函數:

inline fun test() { //warn 內聯函數最適用於具備lambda參數的函數
複製代碼

NOTE:內聯函數不支持局部函數

禁用內聯

對於具備多個lambda參數的內聯函數來講,默認內聯, 可用 noinline 修飾lambda參數,禁用內聯。

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
  // ……
}
複製代碼

noinline 僅在內聯函數中可以使用

inline fun foo(noinline notInlined: () -> Unit) {
  // …… 僅一個參數又用 noinline 修飾, inline 將無效
}
複製代碼

非局部返回

lambda 表達式內部不容許無標籤的return , 但傳給的函數是內聯的,該 return 也能夠內聯,因此它是容許返回。稱爲非局部返回

fun <T> lock(body: () -> T): Unit {
}
inline fun <T> lockInline(body: () -> T): Unit {
}

lock {
    return  // 不容許不帶標籤的return. return@lock
}
lockInline{
  return
}
複製代碼

循環中經常使用這種結構

fun hasZeros(ints: List<Int>): Boolean {
  ints.forEach {
    if (it == 0) return true // 從 hasZeros 返回
  }
  return false
}
複製代碼

一些內聯函數可能調用傳給它們的不是直接來自函數體、而是來自另外一個執行上下文的 lambda 表達式參數,例如來自局部對象或嵌套函數。在這種狀況下,該 lambda 表達式中也不容許非局部控制流。爲了標識這種狀況,該 lambda 表達式參數須要用 crossinline 修飾符標記:

inline fun post(crossinline body: () -> Unit) {
  Runnable { body() }
}
複製代碼

具體化的類型參數

內聯函數能有具體化的類型參數(Reified type parameters),用 reified 修飾符來限定類型參數

在前面泛型函數學習中,是不能有具體化參數,獲取 class時帶來不便。

fun <T> findType(t: T) {
  //由於T 不是靜態已知的 Kotlin 類的引用,因此不能 T::class
  println((t as Any)::class)
}

//內聯函數支持具體化的類型參數,不須要反射,正常的操做符如 !is 和 as 能正常使用
inline fun <reified T : Number> findReifiedType(t: T) {
  println(T::class)
  println(Int.MIN_VALUE is T)
}
複製代碼

內聯屬性

inline 修飾符還能夠修飾沒有幕後字段的屬性的訪問器(有setter/getter),可單獨標註。

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 其中一個標註爲inline, 都不能使用 backing field
inline set(value) {
  count = value
}
//
inline var doubleCounter
get() = count * 2 //set/get 其中一個標註爲inline, 都不能使用 backing field
set(value) {
  count *= value
}
複製代碼

公有 API 內聯函數的限制

當一個內聯函數是 publicprotected 而不是 privateinternal 聲明的一部分時,就會認爲它是一個模塊級的公有 API。能夠在其餘模塊中調用它,而且也能夠在調用處內聯這樣的調用。

這帶來了一些由模塊作這樣變動時致使的二進制兼容的風險——聲明一個內聯函數但調用它的模塊在它修改後並無從新編譯。

爲了消除這種由公有 API 變動引入的不兼容的風險,公有 API 內聯函數體內不容許使用非公有聲明,即,不容許使用 privateinternal 聲明以及其部件。

一個 internal 聲明能夠由 @PublishedApi 標註,這會容許它在公有 API 內聯函數中使用。當一個 internal 內聯函數標記有 @PublishedApi 時,也會像公有函數同樣檢查其函數體。

//公有 API 內聯函數限制使用private 與 internal 聲明以及其部件 (頂層聲明)
inline fun publishApi(body: () -> Unit) {
    privateFun()
    internalFun()
}

@PublishedApi //檢查其函數體加以限制
internal inline fun internalApi(body: () -> Unit) {
    privateFun()
    internalFun()
}

private fun privateFun(): Unit {

}

internal fun internalFun(): Unit {

}
複製代碼

Kotlin與 Java 混合開發

Kotlin 中調用 Java

已映射類型

在Kotlin 中使用Java 代碼,編譯期間, Java 的原生類型映射到相應的 Kotlin 類型,運行時表示保持不變。

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

一些非原生的內置類型也會做映射:

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!平臺類型表示法

集合類型在 Kotlin 中能夠是隻讀的或可變的,所以 Java 集合類型做以下映射: (下表中的全部 Kotlin 類型都駐留在 kotlin.collections包中):

Java 類型 Kotlin 只讀類型 Kotlin 可變類型 加載的平臺類型
Iterator Iterator MutableIterator (Mutable)Iterator!
Iterable Iterable MutableIterable (Mutable)Iterable!
Collection Collection MutableCollection (Mutable)Collection!
Set Set MutableSet (Mutable)Set!
List List MutableList (Mutable)List!
ListIterator ListIterator MutableListIterator (Mutable)ListIterator!
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>!

請注意,用做類型參數的裝箱原始類型映射到平臺類型: 例如,List<java.lang.Integer> 在 Kotlin 中會成爲 List<Int!>

Java 的數組按以下所述映射:

Java 類型 Kotlin 類型
int[] kotlin.IntArray!
String[] kotlin.Array<(out) String>!

空安全和平臺類型

Java 中任何引用均可能是 null,而Kotlin 類型安全(空安全)。 Java 聲明的類型在 Kotlin 中空檢查跟Java相同(可空,非空)稱爲平臺類型。平臺類型可用助記符加在後面來表示,但切記不能在程序中這樣寫,kotlin 並無相應語法,IDE Doc 能夠顯示。

val nullAny = JavaDataType.nullObj //實際: val nullAny: Any!
val safeNullAny: Any? = JavaDataType.nullObj

println(safeNullAny?.hashCode())
println(nullAny?.hashCode()) //null check

val notNullAny: Any = JavaDataType.nullObj //賦值時 NPE
nullAny.hashCode() //使用時 NPE
複製代碼

NOTE:只要不是Java基本類型,在Kotlin中都會映射爲 T!

Getter 和 Setter

遵循 Java 約定的 getter 和 setter 的方法(名稱以 get 開頭的無參數方法和以 set 開頭的單參數方法)在 Kotlin 中表示爲屬性。 Boolean 訪問器方法(其中 getter 的名稱以 is 開頭而 setter 的名稱以 set 開頭)會表示爲與 getter 方法具備相同名稱的屬性。 例如:

import java.util.Calendar

fun calendarDemo() {
    val calendar = Calendar.getInstance()
    if (calendar.firstDayOfWeek == Calendar.SUNDAY) {  // 調用 getFirstDayOfWeek()
        calendar.firstDayOfWeek = Calendar.MONDAY      // 調用ll setFirstDayOfWeek()
    }
    if (!calendar.isLenient) {                         // 調用 isLenient()
        calendar.isLenient = true                      // 調用 setLenient()
    }
}

複製代碼

請注意,若是 Java 類只有一個 setter,它在 Kotlin 中不會做爲屬性可見,由於 Kotlin 目前不支持只寫(set-only)屬性。

返回 void 的方法

若是一個 Java 方法返回 void,那麼從 Kotlin 調用時中返回 Unit。 萬一有人使用其返回值,它將由 Kotlin 編譯器在調用處賦值, 由於該值自己是預先知道的(是 Unit)。

將 Kotlin 中是關鍵字的 Java 標識符進行轉義

一些 Kotlin 關鍵字在 Java 中是有效標識符:inobjectis 等等。 若是一個 Java 庫使用了 Kotlin 關鍵字做爲方法,你仍然能夠經過反引號(`)字符轉義它來調用該方法

foo.`is`(bar)
複製代碼

Java 泛型

Kotlin 的泛型與 Java 有點不一樣(參見泛型)。當將 Java 類型導入 Kotlin 時,咱們會執行一些轉換:

  • Java 的通配符轉換成類型投影
    • Foo<? extends Bar> 轉換成 Foo<out Bar!>!
    • Foo<? super Bar> 轉換成 Foo<in Bar!>!
  • Java的原始類型轉換成星投影
    • List 轉換成 List<*>!,即 List<out Any?>!

和 Java 同樣,Kotlin 在運行時不保留泛型,即對象不攜帶傳遞到他們構造器中的那些類型參數的實際類型。 即 ArrayList<Integer>()ArrayList<Character>() 是不能區分的。 這使得執行 is-檢測不可能照顧到泛型。 Kotlin 只容許 is-檢測星投影的泛型類型:

if (a is List<Int>) // 錯誤:沒法檢查它是否真的是一個 Int 列表
// but
if (a is List<*>) // OK:不保證列表的內容
複製代碼

Java 集合

java 集合類型映射的平臺類型都是可變的,用法如kotlin 同樣,並且 操做符約定一樣有效

Java 數組

與 Java 不一樣,Kotlin 中的數組是不型變的。這意味着 Kotlin 不容許咱們把一個 Array<String> 賦值給一個 Array<Any>, 從而避免了可能的運行時故障。Kotlin 也禁止咱們把一個子類的數組當作超類的數組傳遞給 Kotlin 的方法, 可是對於 Java 方法,這是容許的(經過 Array<(out) String>! 這種形式的平臺類型)。

Java 平臺上,數組會使用原生數據類型以免裝箱/拆箱操做的開銷。 因爲 Kotlin 隱藏了這些實現細節,所以須要一個變通方法來與 Java 代碼進行交互。 對於每種原生類型的數組都有一個特化的類(IntArrayDoubleArrayCharArray 等等)來處理這種狀況。 它們與 Array 類無關,而且會編譯成 Java 原生類型數組以得到最佳性能。

假設有一個接受 int 數組索引的 Java 方法:

public class JavaArrayExample {

    public void removeIndices(int[] indices) {
        // 在此編碼……
    }
}
複製代碼

在 Kotlin 中你能夠這樣傳遞一個原生類型的數組:

val javaObj = JavaArrayExample()
val array = intArrayOf(0, 1, 2, 3)
javaObj.removeIndices(array)  // 將 int[] 傳給方法
複製代碼

當編譯爲 JVM 字節代碼時,編譯器會優化對數組的訪問,這樣就不會引入任何開銷:

val array = arrayOf(1, 2, 3, 4)
array[x] = array[x] * 2 // 不會實際生成對 get() 和 set() 的調用
for (x in array) { // 不會建立迭代器
    print(x)
}
複製代碼

即便當咱們使用索引定位時,也不會引入任何開銷

for (i in array.indices) {// 不會建立迭代器
    array[i] += 2
}
複製代碼

最後,in-檢測也沒有額外開銷

if (i in array.indices) { // 同 (i >= 0 && i < array.size)
    print(array[i])
}
複製代碼

Java 可變參數

Java 類有時聲明一個具備可變數量參數(varargs)的方法來使用索引。

public class JavaArrayExample {

    public void removeIndicesVarArg(int... indices) {
        // 在此編碼……
    }
}
複製代碼

在這種狀況下,你須要使用展開運算符 * 來傳遞 IntArray

val javaObj = JavaArrayExample()
val array = intArrayOf(0, 1, 2, 3)
javaObj.removeIndicesVarArg(*array)
複製代碼

目前沒法傳遞 null 給一個聲明爲可變參數的方法。

操做符

因爲 Java 沒法標記用於運算符語法的方法,Kotlin 容許具備正確名稱和簽名的任何 Java 方法做爲運算符重載和其餘約定(invoke() 等)使用。 不容許使用中綴調用語法調用 Java 方法。

受檢異常

在 Kotlin 中,全部異常都是非受檢的,這意味着編譯器不會強迫你捕獲其中的任何一個。 所以,當你調用一個聲明受檢異常的 Java 方法時,Kotlin 不會強迫你作任何事情:

對象方法

當 Java 類型導入到 Kotlin 中時,類型 java.lang.Object 的全部引用都成了 Any。 而由於 Any 不是平臺指定的,它只聲明瞭 toString()hashCode()equals() 做爲其成員, 因此爲了能用到 java.lang.Object 的其餘成員,Kotlin 要用到擴展函數

wait()/notify()

Effective Java 第 69 條善意地建議優先使用併發工具(concurrency utilities)而不是 wait()notify()。 所以,類型 Any 的引用不提供這兩個方法。 若是你真的須要調用它們的話,你能夠將其轉換爲 java.lang.Object

(foo as java.lang.Object).wait()
複製代碼
getClass()

要取得對象的 Java 類,請在類引用上使用 java 擴展屬性。

val fooClass = foo::class.java
複製代碼

上面的代碼使用了自 Kotlin 1.1 起支持的綁定的類引用。你也可使用 javaClass 擴展屬性。

val fooClass = foo.javaClass
複製代碼
clone()

要覆蓋 clone(),須要繼承 kotlin.Cloneable

class Example : Cloneable {
    override fun clone(): Any { …… }
}
複製代碼

不要忘記 Effective Java 的第 11 條: 謹慎地改寫clone

finalize()

要覆蓋 finalize(),全部你須要作的就是簡單地聲明它,而不須要 override 關鍵字:

class C {
    protected fun finalize() {
        // 終止化邏輯
    }
}
複製代碼

根據 Java 的規則,finalize() 不能是 private 的。

訪問靜態成員

Java 類的靜態成員會造成該類的「伴生對象」。咱們沒法將這樣的「伴生對象」做爲值來傳遞, 但能夠顯式訪問其成員,例如:

val character = Character
if (Character.isLetter('A')) {
  // ……
}
複製代碼

Java 反射

Java 反射適用於 Kotlin 類,反之亦然。如上所述,你可使用 instance::class.java,ClassName::class.java 或者 instance.javaClass 經過 java.lang.Class 來進入 Java 反射。

其餘支持的狀況包括爲一個 Kotlin 屬性獲取一個 Java 的 getter/setter 方法或者幕後字段、爲一個 Java 字段獲取一個 KProperty、爲一個 KFunction 獲取一個 Java 方法或者構造函數,反之亦然。

SAM 轉換

就像 Java 8 同樣,Kotlin 支持 SAM 轉換。這意味着 Kotlin 函數字面值能夠被自動的轉換成只有一個非默認方法的 Java 接口的實現,只要這個方法的參數類型可以與這個 Kotlin 函數的參數類型相匹配。

你能夠這樣建立 SAM 接口的實例:

val runnable = Runnable { println("This runs in a runnable") }

複製代碼

……以及在方法調用中:

val executor = ThreadPoolExecutor()
// Java 簽名:void execute(Runnable command)
executor.execute { println("This runs in a thread pool") }

複製代碼

若是 Java 類有多個接受函數式接口的方法,那麼能夠經過使用將 lambda 表達式轉換爲特定的 SAM 類型的適配器函數來選擇須要調用的方法。這些適配器函數也會按需由編譯器生成。

executor.execute(Runnable { println("This runs in a thread pool") })

複製代碼

請注意,SAM 轉換隻適用於接口,而不適用於抽象類,即便這些抽象類也只有一個抽象方法。

還要注意,此功能只適用於 Java 互操做;由於 Kotlin 具備合適的函數類型,因此不須要將函數自動轉換爲 Kotlin 接口的實現,所以不受支持。

Java中調用 Kotlin

Java 能夠輕鬆調用 Kotlin 代碼。

屬性

Kotlin 屬性會編譯成如下 Java 元素:

  • 一個 getter 方法,名稱經過加前綴 get 算出;
  • 一個 setter 方法,名稱經過加前綴 set 算出(只適用於 var 屬性);
  • 一個私有字段,與屬性名稱相同(僅適用於具備幕後字段的屬性)。

例如,var firstName: String 編譯成如下 Java 聲明:

private String firstName;

public String getFirstName() {
    return firstName;
}

public void setFirstName(String firstName) {
    this.firstName = firstName;
}

複製代碼

若是屬性的名稱以 is 開頭,則使用不一樣的名稱映射規則:getter 的名稱與屬性名稱相同,而且 setter 的名稱是經過將 is 替換爲 set 得到。 例如,對於屬性 isOpen,其 getter 會稱作 isOpen(),而其 setter 會稱作 setOpen()。 這一規則適用於任何類型的屬性,並不只限於 Boolean

包級函數

org.foo.bar 包內的 example.kt 文件中聲明的全部的函數和屬性,包括擴展函數, 都編譯成一個名爲 org.foo.bar.ExampleKt 的 Java 類的靜態方法。

// 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();

複製代碼

若是多個文件中生成了相同的 Java 類名(包名相同而且類名相同或者有相同的 @JvmName 註解)一般是錯誤的。然而,編譯器可以生成一個單一的 Java 外觀類,它具備指定的名稱且包含來自全部文件中具備該名稱的全部聲明。 要啓用生成這樣的外觀,請在全部相關文件中使用 @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();

複製代碼

實例字段

若是須要在 Java 中將 Kotlin 屬性做爲字段暴露,那就須要使用 @JvmField 註解對其標註。 該字段將具備與底層屬性相同的可見性。若是一個屬性有幕後字段(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中)也會暴露爲字段。 該字段的可見性與 lateinit 屬性的 setter 相同。

靜態字段

在命名對象或伴生對象中聲明的 Kotlin 屬性會在該命名對象或包含伴生對象的類中具備靜態幕後字段。

一般這些字段是私有的,但能夠經過如下方式之一暴露出來:

  • @JvmField 註解;
  • lateinit 修飾符;
  • const 修飾符。

使用 @JvmField 標註這樣的屬性使其成爲與屬性自己具備相同可見性的靜態字段。

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 字段

複製代碼

在命名對象或者伴生對象中的一個延遲初始化的屬性具備與屬性 setter 相同可見性的靜態幕後字段。

object Singleton {
    lateinit var provider: Provider
}

複製代碼
// Java
Singleton.provider = new Provider();
// 在 Singleton 類中的 public static 非-final 字段

複製代碼

const 標註的(在類中以及在頂層的)屬性在 Java 中會成爲靜態字段:

// 文件 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;

複製代碼

靜態方法

如上所述,Kotlin 將包級函數表示爲靜態方法。 Kotlin 還能夠爲命名對象或伴生對象中定義的函數生成靜態方法,若是你將這些函數標註爲 @JvmStatic 的話。 若是你使用該註解,編譯器既會在相應對象的類中生成靜態方法,也會在對象自身中生成實例方法。 例如:

class C {
    companion object {
        @JvmStatic fun foo() {}
        fun bar() {}
    }
}

複製代碼

如今,foo() 在 Java 中是靜態的,而 bar() 不是:

C.foo(); // 沒問題
C.bar(); // 錯誤:不是一個靜態方法
C.Companion.foo(); // 保留實例方法
C.Companion.bar(); // 惟一的工做方式

複製代碼

對於命名對象也一樣:

object Obj {
    @JvmStatic fun foo() {}
    fun bar() {}
}

複製代碼

在 Java 中:

Obj.foo(); // 沒問題
Obj.bar(); // 錯誤
Obj.INSTANCE.bar(); // 沒問題,經過單例實例調用
Obj.INSTANCE.foo(); // 也沒問題

複製代碼

@JvmStatic 註解也能夠應用於對象或伴生對象的屬性, 使其 getter 和 setter 方法在該對象或包含該伴生對象的類中是靜態成員。

可見性

Kotlin 的可見性如下列方式映射到 Java:

  • private 成員編譯成 private 成員;
  • private 的頂層聲明編譯成包級局部聲明;
  • protected 保持 protected(注意 Java 容許訪問同一個包中其餘類的受保護成員, 而 Kotlin 不能,因此 Java 類會訪問更普遍的代碼);
  • internal 聲明會成爲 Java 中的 publicinternal 類的成員會經過名字修飾,使其更難以在 Java 中意外使用到,而且根據 Kotlin 規則使其容許重載相同簽名的成員而互不可見;
  • public 保持 public

KClass

有時你須要調用有 KClass 類型參數的 Kotlin 方法。 由於沒有從 ClassKClass 的自動轉換,因此你必須經過調用 Class<T>.kotlin 擴展屬性的等價形式來手動進行轉換:

kotlin.jvm.JvmClassMappingKt.getKotlinClass(MainView.class)

複製代碼

用 @JvmName 解決簽名衝突

有時咱們想讓一個 Kotlin 中的命名函數在字節碼中有另一個 JVM 名稱。 最突出的例子是因爲類型擦除引起的:

fun List<String>.filterValid(): List<String>
fun List<Int>.filterValid(): List<Int>

複製代碼

這兩個函數不能同時定義,由於它們的 JVM 簽名是同樣的:filterValid(Ljava/util/List;)Ljava/util/List;。 若是咱們真的但願它們在 Kotlin 中用相同名稱,咱們須要用 @JvmName 去標註其中的一個(或兩個),並指定不一樣的名稱做爲參數:

fun List<String>.filterValid(): List<String>

@JvmName("filterValidInt")
fun List<Int>.filterValid(): List<Int>

複製代碼

在 Kotlin 中它們能夠用相同的名稱 filterValid 來訪問,而在 Java 中,它們分別是 filterValidfilterValidInt

一樣的技巧也適用於屬性 x 和函數 getX() 共存:

val x: Int
    @JvmName("getX_prop")
    get() = 15

fun getX() = 10

複製代碼

生成重載

一般,若是你寫一個有默認參數值的 Kotlin 函數,在 Java 中只會有一個全部參數都存在的完整參數簽名的方法可見,若是但願向 Java 調用者暴露多個重載,可使用 @JvmOverloads 註解。

該註解也適用於構造函數、靜態方法等。它不能用於抽象方法,包括在接口中定義的方法。

class Foo @JvmOverloads constructor(x: Int, y: Double = 0.0) {
    @JvmOverloads fun f(a: String, b: Int = 0, c: String = "abc") {
        ……
    }
}

複製代碼

對於每個有默認值的參數,都會生成一個額外的重載,這個重載會把這個參數和它右邊的全部參數都移除掉。在上例中,會生成如下代碼 :

// 構造函數:
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) { }

複製代碼

請注意,如次構造函數中所述,若是一個類的全部構造函數參數都有默認值,那麼會爲其生成一個公有的無參構造函數。這就算沒有 @JvmOverloads 註解也有效。

受檢異常

如上所述,Kotlin 沒有受檢異常。 因此,一般 Kotlin 函數的 Java 簽名不會聲明拋出異常。 因而若是咱們有一個這樣的 Kotlin 函數:

// example.kt
package demo

fun foo() {
    throw IOException()
}

複製代碼

而後咱們想要在 Java 中調用它並捕捉這個異常:

// Java
try {
  demo.Example.foo();
}
catch (IOException e) { // 錯誤:foo() 未在 throws 列表中聲明 IOException
  // ……
}

複製代碼

由於 foo() 沒有聲明 IOException,咱們從 Java 編譯器獲得了一個報錯消息。 爲了解決這個問題,要在 Kotlin 中使用 @Throws 註解。

@Throws(IOException::class)
fun foo() {
    throw IOException()
}

複製代碼

空安全性

當從 Java 中調用 Kotlin 函數時,沒人阻止咱們將 null 做爲非空參數傳遞。 這就是爲何 Kotlin 給全部指望非空參數的公有函數生成運行時檢測。 這樣咱們就能在 Java 代碼裏當即獲得 NullPointerException

型變的泛型

當 Kotlin 的類使用了聲明處型變,有兩種選擇能夠從 Java 代碼中看到它們的用法。讓咱們假設咱們有如下類和兩個使用它的函數:

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

複製代碼

一種看似理所固然地將這倆函數轉換成 Java 代碼的方式可能會是:

Box<Derived> boxDerived(Derived value) { …… }
Base unboxBase(Box<Base> box) { …… }

複製代碼

問題是,在 Kotlin 中咱們能夠這樣寫 unboxBase(boxDerived("s")),可是在 Java 中是行不通的,由於在 Java 中類 Box 在其泛型參數 T 上是不型變的,因而 Box<Derived> 並非 Box<Base> 的子類。 要使其在 Java 中工做,咱們按如下這樣定義 unboxBase

Base unboxBase(Box<? extends Base> box) { …… }  

複製代碼

這裏咱們使用 Java 的通配符類型? extends Base)來經過使用處型變來模擬聲明處型變,由於在 Java 中只能這樣。

當它做爲參數出現時,爲了讓 Kotlin 的 API 在 Java 中工做,對於協變定義的 Box 咱們生成 Box<Super> 做爲 Box<? extends Super> (或者對於逆變定義的 Foo 生成 Foo<? super Bar>)。當它是一個返回值時, 咱們不生成通配符,由於不然 Java 客戶端將必須處理它們(而且它違反經常使用 Java 編碼風格)。所以,咱們的示例中的對應函數實際上翻譯以下:

// 做爲返回類型——沒有通配符
Box<Derived> boxDerived(Derived value) { …… }
 
// 做爲參數——有通配符
Base unboxBase(Box<? extends Base> box) { …… }

複製代碼

注意:當參數類型是 final 時,生成通配符一般沒有意義,因此不管在什麼地方 Box<String> 始終轉換爲 Box<String>

若是咱們在默認不生成通配符的地方須要通配符,咱們可使用 @JvmWildcard 註解:

fun boxDerived(value: Derived): Box<@JvmWildcard Derived> = Box(value)
// 將被轉換成
// Box<? extends Derived> boxDerived(Derived value) { …… }

複製代碼

另外一方面,若是咱們根本不須要默認的通配符轉換,咱們可使用@JvmSuppressWildcards

fun unboxBase(box: Box<@JvmSuppressWildcards Base>): Base = box.value
// 會翻譯成
// Base unboxBase(Box<Base> box) { …… }

複製代碼

注意:@JvmSuppressWildcards 不只可用於單個類型參數,還可用於整個聲明(如函數或類),從而抑制其中的全部通配符。

Nothing 類型翻譯

類型 Nothing 是特殊的,由於它在 Java 中沒有天然的對應。確實,每一個 Java 引用類型,包括java.lang.Void 均可以接受 null 值,可是 Nothing 不行。所以,這種類型不能在 Java 世界中準確表示。這就是爲何在使用 Nothing 參數的地方 Kotlin 生成一個原始類型:

fun emptyList(): List<Nothing> = listOf()
// 會翻譯成
// List emptyList() { …… }
複製代碼

在 Kotlin 中使用 JNI

要聲明一個在本地(C 或 C++)代碼中實現的函數,你須要使用 external 修飾符來標記它:

external fun foo(x: Int): Double
複製代碼

其他的過程與 Java 中的工做方式徹底相同。

Kotlin 高級編程

領域特定語言 DSL

域特定語言(DSL)的基本思想是針對特定類型的問題的計算機語言,而不是面向任何類型的軟件問題的通用語言。

類型安全的構建器

構建器(builder)的概念在 Groovy 社區中很是熱門。 構建器容許以半聲明(semi-declarative)的方式定義數據。構建器很適合用來生成 XML佈局 UI 組件描述 3D 場景以及其餘更多功能……

Kotlin 容許檢查類型的構建器,比 Groovy 自身的動態類型實現更具吸引力。

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(href = "http://jetbrains.com/kotlin") { +"Kotlin" }

                    // mixed content
                    p {
                        +"This is some"
                        b { +"mixed" }
                        +"text. For more see the"
                        a(href = "http://jetbrains.com/kotlin") { +"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
}

複製代碼

上面實現 HTML 標籤,其實是調用一個 lambda函數,用一個標籤接收者的函數類型zuo做爲參數,使在函數內部調用該實例的成員。

幾個厲害的 DSL 項目

  • Anko 用於 Android 的,用於描述 UI 。
  • Gensokyo 用於 Swing 的,用於描述 UI
  • KotlinTest Kotlin測試框架基於優秀的Scalatest

協程 Coroutine

在 Kotlin 1.1 中協程是實驗性的。另外kotlin 爲了減小程序體積,根據須要使用協程,你要加入kotlinx-coroutines-core 庫.

一些 API 啓動長時間運行的操做(例如網絡 IO、文件 IO、CPU 或 GPU 密集型任務等),並要求調用者阻塞直到它們完成。協程提供了一種避免阻塞線程並用更廉價、更可控的操做替代線程阻塞的方法:協程掛起

協程經過將複雜性放入庫來簡化異步編程。程序的邏輯能夠在協程中順序地表達,而底層庫會爲咱們解決其異步性。該庫能夠將用戶代碼的相關部分包裝爲回調、訂閱相關事件、在不一樣線程(甚至不一樣機器!)上調度執行,而代碼則保持如同順序執行同樣簡單。

許多在其餘語言中可用的異步機制可使用 Kotlin 協程實現爲庫。這包括源於 C# 和 ECMAScript 的 async/await、源於 Go 的 管道select 以及源於 C# 和 Python 生成器/yield。關於提供這些結構的庫請參見其下文描述。

阻塞 vs 掛起

基本上,協程計算能夠被掛起而無需阻塞線程。線程阻塞的代價一般是昂貴的,尤爲在高負載時,由於只有相對少許線程實際可用,所以阻塞其中一個會致使一些重要的任務被延遲。

另外一方面,協程掛起幾乎是無代價的。不須要上下文切換或者 OS 的任何其餘干預。最重要的是,掛起能夠在很大程度上由用戶庫控制:做爲庫的做者,咱們能夠決定掛起時發生什麼並根據需求優化/記日誌/截獲。

另外一個區別是,協程不能在隨機的指令中掛起,而只能在所謂的掛起點掛起,這會調用特別標記的函數。

掛起函數

當咱們調用標記有特殊修飾符 suspend 的函數時,會發生掛起:

suspend fun doSomething(foo: Foo): Bar {
    ……
}
複製代碼

這樣的函數稱爲掛起函數,由於調用它們可能掛起協程(若是相關調用的結果已經可用,庫能夠決定繼續進行而不掛起)。掛起函數可以以與普通函數相同的方式獲取參數和返回值,但它們只能從協程和其餘掛起函數中調用。事實上,要啓動協程,必須至少有一個掛起函數,它一般是匿名的(即它是一個掛起 lambda 表達式)。讓咱們來看一個例子,一個簡化的 async() 函數(源自 kotlinx.coroutines 庫):

fun <T> async(block: suspend () -> T)
複製代碼

這裏的 async() 是一個普通函數(不是掛起函數),可是它的 block 參數具備一個帶 suspend 修飾符的函數類型: suspend () -> T。因此,當咱們將一個 lambda 表達式傳給 async() 時,它會是掛起 lambda 表達式,因而咱們能夠從中調用掛起函數:

async {
    doSomething(foo)
    ……
}
複製代碼

繼續該類比,await() 能夠是一個掛起函數(所以也能夠在一個 async {} 塊中調用),該函數掛起一個協程,直到一些計算完成並返回其結果:

async {
    ……
    val result = computation.await()
    ……
}
複製代碼

更多關於 async/await 函數實際在 kotlinx.coroutines 中如何工做的信息能夠在這裏找到。

請注意,掛起函數 await()doSomething() 不能在像 main() 這樣的普通函數中調用:

fun main(args: Array<String>) {
    doSomething() // 錯誤:掛起函數從非協程上下文調用
}
複製代碼

還要注意的是,掛起函數能夠是虛擬的,當覆蓋它們時,必須指定 suspend 修飾符:

interface Base {
    suspend fun foo()
}

class Derived: Base {
    override suspend fun foo() { …… }
}
複製代碼

@RestrictsSuspension 註解

擴展函數(和 lambda 表達式)也能夠標記爲 suspend,就像普通的同樣。這容許建立 DSL 及其餘用戶可擴展的 API。在某些狀況下,庫做者須要阻止用戶添加新方式來掛起協程。

爲了實現這一點,可使用 @RestrictsSuspension 註解。當接收者類/接口 R 用它標註時,全部掛起擴展都須要委託給 R 的成員或其它委託給它的擴展。因爲擴展不能無限相互委託(程序不會終止),這保證全部掛起都經過調用 R 的成員發生,庫的做者就能夠徹底控制了。

這在少數狀況是須要的,當每次掛起在庫中以特殊方式處理時。例如,當經過 buildSequence() 函數實現下文所述的生成器時,咱們須要確保在協程中的任何掛起調用最終調用 yield()yieldAll() 而不是任何其餘函數。這就是爲何 SequenceBuilder@RestrictsSuspension 註解:

@RestrictsSuspension
public abstract class SequenceBuilder<in T> {
    ……
}
複製代碼

參見其 Github 上 的源代碼。

協程的內部機制

咱們不是在這裏給出一個關於協程如何工做的完整解釋,然而粗略地認識發生了什麼是至關重要的。

協程徹底經過編譯技術實現(不須要來自 VM 或 OS 端的支持),掛起經過代碼來生效。基本上,每一個掛起函數(優化可能適用,但咱們不在這裏討論)都轉換爲狀態機,其中的狀態對應於掛起調用。恰好在掛起前,下一狀態與相關局部變量等一塊兒存儲在編譯器生成的類的字段中。在恢復該協程時,恢復局部變量而且狀態機從恰好掛起以後的狀態進行。

掛起的協程能夠做爲保持其掛起狀態與局部變量的對象來存儲和傳遞。這種對象的類型是 Continuation,而這裏描述的整個代碼轉換對應於經典的延續性傳遞風格(Continuation-passing style)。所以,掛起函數有一個 Continuation 類型的額外參數做爲高級選項。

關於協程工做原理的更多細節能夠在這個設計文檔中找到。在其餘語言(如 C# 或者 ECMAScript 2016)中的 async/await 的相似描述與此相關,雖然它們實現的語言功能可能不像 Kotlin 協程這樣通用。

協程的實驗性狀態

協程的設計是實驗性的,這意味着它可能在即將發佈的版本中更改。當在 Kotlin 1.1 中編譯協程時,默認狀況下會報一個警告:「協程」功能是實驗性的。要移出該警告,你須要指定 opt-in 標誌

因爲其實驗性狀態,標準庫中協程相關的 API 放在 kotlin.coroutines.experimental 包下。當設計完成而且實驗性狀態解除時,最終的 API 會移動到 kotlin.coroutines,而且實驗包會被保留(可能在一個單獨的構件中)以實現向後兼容。

重要注意事項:咱們建議庫做者遵循相同慣例:給暴露基於協程 API 的包添加「experimental」後綴(如 com.example.experimental),以使你的庫保持二進制兼容。當最終 API 發佈時,請按照下列步驟操做:

  • 將全部 API 複製到 com.example(沒有 experimental 後綴),
  • 保持實驗包的向後兼容性。

這將最小化你的用戶的遷移問題。

標準 API

協程有三個主要組成部分:

  • 語言支持(即如上所述的掛起功能),
  • Kotlin 標準庫中的底層核心 API,
  • 能夠直接在用戶代碼中使用的高級 API。

底層 API:kotlin.coroutines

底層 API 相對較小,而且除了建立更高級的庫以外,不該該使用它。 它由兩個主要包組成:

關於這些 API 用法的更多細節能夠在這裏找到。

kotlin.coroutines 中的生成器 API

kotlin.coroutines.experimental 中僅有的「應用程序級」函數是

這些包含在 kotlin-stdlib 中由於他們與序列相關。這些函數(咱們能夠僅限於這裏的 buildSequence())實現了 生成器 ,即提供一種廉價構建惰性序列的方法:

val fibonacciSeq = buildSequence {
    var a = 0
    var b = 1

    yield(1)

    while (true) {
        yield(a + b)

        val tmp = a + b
        a = b
        b = tmp
    }
}
複製代碼

這經過建立一個協程生成一個惰性的、潛在無限的斐波那契數列,該協程經過調用 yield() 函數來產生連續的斐波納契數。當在這樣的序列的迭代器上迭代每一步,都會執行生成下一個數的協程的另外一部分。所以,咱們能夠從該序列中取出任何有限的數字列表,例如 fibonacciSeq.take(8).toList() 結果是 [1, 1, 2, 3, 5, 8, 13, 21]。協程足夠廉價使這很實用。

爲了演示這樣一個序列的真正惰性,讓咱們在調用 buildSequence() 內部輸出一些調試信息:

val lazySeq = buildSequence {
    print("START ")
    for (i in 1..5) {
        yield(i)
        print("STEP ")
    }
    print("END")
}

// 輸出序列的前三個元素
lazySeq.take(3).forEach { print("$it ") }
複製代碼

運行上面的代碼看,是否是咱們輸出前三個元素的數字與生成循環的 STEP 有交叉。這意味着計算確實是惰性的。要輸出 1,咱們只執行到第一個 yield(i),而且過程當中會輸出 START。而後,輸出 2,咱們須要繼續下一個 yield(i),並會輸出 STEP3 也同樣。永遠不會輸出再下一個 STEP(以及END),由於咱們再也沒有請求序列的後續元素。

爲了一次產生值的集合(或序列),可使用 yieldAll() 函數:

val lazySeq = buildSequence {
    yield(0)
    yieldAll(1..10) 
}

lazySeq.forEach { print("$it ") }
複製代碼

buildIterator() 的工做方式相似於 buildSequence(),但返回一個惰性迭代器。

能夠經過爲 SequenceBuilder 類寫掛起擴展(帶有上文描述的 @RestrictsSuspension 註解)來爲 buildSequence() 添加自定義生產邏輯(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)
}
複製代碼

其餘高級 API:kotlinx.coroutines

只有與協程相關的核心 API 能夠從 Kotlin 標準庫得到。這主要包括全部基於協程的庫可能使用的核心原語和接口。

大多數基於協程的應用程序級API都做爲單獨的庫發佈:kotlinx.coroutines。這個庫覆蓋了

  • 使用kotlinx-coroutines-core的平臺無關異步編程

    • 此模塊包括支持 select 和其餘便利原語的相似 Go 的管道
    • 這個庫的綜合指南在這裏
  • 基於 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

這些庫既做爲使通用任務易用的便利的 API,也做爲如何構建基於協程的庫的端到端示例。

更多

集合

與大多數語言不一樣,Kotlin 區分可變集合和不可變集合(lists、sets、maps 等)。精確控制何時集合可編輯有助於消除 bug 和設計良好的 API。

預先了解一個可變集合的只讀 視圖 和一個真正的不可變集合之間的區別是很重要的。它們都容易建立,但類型系統不能表達它們的差異,因此由你來跟蹤(是否相關)。

Kotlin 的 List<out T> 類型是一個提供只讀操做如 sizeget等的接口。和 Java 相似,它繼承自 Collection<T> 進而繼承自 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 沒有專門的語法結構建立 list 或 set。 要用標準庫的方法,如 listOf()mutableListOf()setOf()mutableSetOf()。 在非性能關鍵代碼中建立 map 能夠用一個簡單的慣用法來完成:mapOf(a to b, c to d)

注意上面的 readOnlyView 變量(譯者注:與對應可變集合變量 numbers)指向相同的底層 list 並會隨之改變。 若是一個 list 只存在只讀引用,咱們能夠考慮該集合徹底不可變。建立一個這樣的集合的一個簡單方式以下:

val items = listOf(1, 2, 3)
複製代碼

目前 listOf 方法是使用 array list 實現的,可是將來能夠利用它們知道本身不能變的事實,返回更節約內存的徹底不可變的集合類型。

注意這些類型是協變的。這意味着,你能夠把一個 List<Rectangle> 賦值給 List<Shape> 假定 Rectangle 繼承自 Shape。對於可變集合類型這是不容許的,由於這將致使運行時故障。

有時你想給調用者返回一個集合在某個特定時間的一個快照, 一個保證不會變的:

class Controller {
    private val _items = mutableListOf<String>()
    val items: List<String> get() = _items.toList()
}
複製代碼

這個 toList 擴展方法只是複製列表項,所以返回的 list 保證永遠不會改變。

List 和 set 有不少有用的擴展方法值得熟悉:

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()
複製代碼

…… 以及全部你所指望的實用工具,例如 sort、zip、fold、reduce 等等。

Map 遵循一樣模式。它們能夠容易地實例化和訪問,像這樣:

val readWriteMap = hashMapOf("foo" to 1, "bar" to 2)
println(readWriteMap["foo"])  // 輸出「1」
val snapshot: Map<String, Int> = HashMap(readWriteMap)
複製代碼

類型安全和智能轉換

空安全

Kotlin 的類型系統

  • 可空類型
  • 非空類型

它消除了不少編程語言(如: Java)來自於代碼空引用,而致使的 NullPointerException 或簡稱 NPE

NOTE: Kotlin 發生 NPE 緣由可能以下:

  • 顯式調用 throw NullPointerException()
  • 使用了下文描述的 !! 操做符
  • 外部 Java 代碼致使的

在上面 變量 中, Kotlin 默認聲明變量時是非空類型的,要使該變量接收 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
}
複製代碼

當聲明可空類型變量時,它是不安全的,訪問方法或屬性時須要做處理:

  • 在條件中檢查 null ,但僅適用於 val 且不可覆蓋(即不能用 open 修飾)或者 get 的不可變的變量。
  • 安全的調用 ?. , 若爲null 則跳過,不然接着調用
  • !! 操做符 ,會返回一個非空的值,不然拋出一個 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()
        }
    }
}
複製代碼

安全調用和!! 操做符對比

cNullUnable.dec() //保證不會致使 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!!
複製代碼

類型檢測和安全的類型轉換

  • is !is 運算符檢測一個表達式是否某類型的一個實例。在許多狀況下,不須要在 Kotlin 中使用顯式轉換操做符,由於編譯器跟蹤不可變值的 is-檢查,並在須要時自動插入(安全的)轉換:
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? 運算符能把對象轉換爲目標類型,常規類型轉換可能會致使 ClassCastException。使用安全的類型轉換符 as?,若是嘗試轉換不成功則返回 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 容許咱們爲本身的類型提供預約義的一組操做符的實現。這些操做符具備固定的符號表示(如 +*)和固定的優先級。爲實現這樣的操做符,咱們爲相應的類型(即二元操做符左側的類型和一元操做符的參數類型)提供了一個固定名字的成員函數擴展函數。 重載操做符的函數須要用 operator 修飾符標記。

另外,咱們描述爲不一樣操做符規範操做符重載的約定。

一元前綴操做符

表達式 翻譯爲
+a a.unaryPlus()
-a a.unaryMinus()
!a a.not()

這個表是說,當編譯器處理例如表達式 +a 時,它執行如下步驟:

  • 肯定 a 的類型,令其爲 T
  • 爲接收者 T 查找一個帶有 operator 修飾符的無參函數 unaryPlus(),即成員函數或擴展函數。
  • 若是函數不存在或不明確,則致使編譯錯誤。
  • 若是函數存在且其返回類型爲 R,那就表達式 +a 具備類型 R

注意 這些操做以及全部其餘操做都針對基本類型作了優化,不會爲它們引入函數調用的開銷。

如下是如何重載一元減運算符的示例:

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)」
複製代碼

遞增與遞減

表達式 翻譯爲
a++ a.inc() + 見下文
a-- a.dec() + 見下文

inc()dec() 函數必須返回一個值,它用於賦值給使用++-- 操做的變量。它們不該該改變在其上調用 inc()dec() 的對象。

編譯器執行如下步驟來解析後綴形式的操做符,例如 a++

  • 肯定 a 的類型,令其爲 T
  • 查找一個適用於類型爲 T 的接收者的、帶有 operator 修飾符的無參數函數 inc()
  • 檢查函數的返回類型是 T 的子類型。

計算表達式的步驟是:

  • a 的初始值存儲到臨時存儲 a0 中,
  • a.inc() 結果賦值給 a
  • a0 做爲表達式的結果返回。

對於 a--,步驟是徹底相似的。

對於前綴形式 ++a--a 以相同方式解析,其步驟是:

  • a.inc() 結果賦值給 a
  • a 的新值做爲表達式結果返回。

二元操做算術運算符

表達式 翻譯爲
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)

對於此表中的操做,編譯器只是解析成翻譯爲列中的表達式。

請注意,自 Kotlin 1.1 起支持 rem 運算符。Kotlin 1.0 使用 mod 運算符,它在 Kotlin 1.1 中被棄用。

示例

下面是一個從給定值起始的 Counter 類的示例,它可使用重載的 + 運算符來增長計數。

data class Counter(val dayIndex: Int) {
    operator fun plus(increment: Int): Counter {
        return Counter(dayIndex + increment)
    }
}
複製代碼

In操做符

表達式 翻譯爲
a in b b.contains(a)
a !in b !b.contains(a)

對於 in!in,過程是相同的,可是參數的順序是相反的。

索引訪問操做符

表達式 翻譯爲
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)

方括號轉換爲調用帶有適當數量參數的 getset

調用操做符

表達式 翻譯爲
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)

圓括號轉換爲調用帶有適當數量參數的 invoke

廣義賦值

表達式 翻譯爲
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)(已棄用)

對於賦值操做,例如 a += b,編譯器執行如下步驟:

  • 若是右列的函數可用
    • 若是相應的二元函數(即 plusAssign() 對應於 plus())也可用,那麼報告錯誤(模糊)。
    • 確保其返回類型是 Unit,不然報告錯誤。
    • 生成 a.plusAssign(b) 的代碼
  • 不然試着生成 a = a + b 的代碼(這裏包含類型檢查:a + b 的類型必須是 a 的子類型)。

注意:賦值在 Kotlin 中不是表達式。

相等與不等操做符

表達式 翻譯爲
a == b a?.equals(b) ?: (b === null)
a != b !(a?.equals(b) ?: (b === null))

注意===!==(同一性檢查)不可重載,所以不存在對他們的約定

這個 == 操做符有些特殊:它被翻譯成一個複雜的表達式,用於篩選 null 值。 null == null 老是 true,對於非空的 xx == null 老是 false 而不會調用 x.equals()

比較操做符

表達式 翻譯爲
a > b a.compareTo(b) > 0
a < b a.compareTo(b) < 0
a >= b a.compareTo(b) >= 0
a <= b a.compareTo(b) <= 0

全部的比較都轉換爲對 compareTo 的調用,這個函數須要返回 Int

屬性委託操做符

provideDelegategetValue 以及 setValue 操做符函數已在委託屬性中描述。

命名函數的中綴調用

咱們能夠經過中綴函數的調用 來模擬自定義中綴操做符。

類型相等性

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

  • 引用相等(兩個引用指向同一對象)

  • 結構相等(用 equals() 檢查)

引用相等

引用相等由 ===(以及其否認形式 !==)操做判斷。a === b 當且僅當 a 和 b 指向同一個對象時求值爲 true。

結構相等

結構相等由 ==(以及其否認形式 !=)操做判斷。按照慣例,像 a == b 這樣的表達式會翻譯成

a?.equals(b) ?: (b === null)

也就是說若是 a 不是 null 則調用 equals(Any?) 函數,不然(即 anull)檢查 b 是否與 null 引用相等。

請注意,當與 null 顯式比較時徹底不必優化你的代碼:a == null 會被自動轉換爲 a=== null。同類型纔有可比性。

This表達式

爲了表示當前的 接收者 咱們使用 this 表達式:

若是 this 沒有限定符,它指的是最內層的包含它的做用域。要引用其餘做用域中的 this,請使用 標籤限定符

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 //內部類名相同,不能用限定的 this

    }


    inner class InnerKotlinThisExpression { // 隱式標籤 @InnerKotlinThisExpression
        fun InnerKotlinThisExpression.fuck() { // 隱式標籤 @fuck
            val a = this@KotlinThisExpression // KotlinThisExpression 的 this
            val b = this@InnerKotlinThisExpression // InnerKotlinThisExpression 的 this

            val c = this // fuck() 的接收者,一個 InnerKotlinThisExpression
            val d = this@fuck // fuck() 的接收者,一個 InnerKotlinThisExpression

            val label = label@ fun String.() {
                println(this)// label 的接收者
            }

            "label".label()
            val lambda = { ->
                // fuck() 的接收者,由於它包含的 lambda 表達式
                // 沒有任何接收者
                println(this)
            }

            lambda()
        }

        fun test() {
            fuck()
        }
    }
}

private fun KotlinThisExpression.leftReference() = this.thisClassObject //this 表示在點左側傳遞的 接收者 參數。

複製代碼

Nothing 類型

若是用 null 來初始化一個要推斷類型的值,而又沒有其餘信息可用於肯定更具體的類型時,編譯器會推斷出 Nothing? 類型:

val nothingInt/*: Nothing?*/ = null
val list:List<Nothing?> = listOf(null)
複製代碼

另外Kotlin 中 throw 是表達式, 表達式的類型是特殊類型 Nothing。 該類型沒有值,而是用於標記永遠不能達到的代碼位置。Nothing 能夠用來標記一個永遠不會返回的函數, 也能夠做爲 Elvis 表達式的一部分:

val nothingInt/*: Nothing?*/ = null
val list: List<Nothing?> = listOf(null)
fun fail(message: String): Nothing {
    throw IllegalArgumentException(message)
}
fail("fail")
//做爲 Elvis 表達式的一部分 
var exception = null ?: throw RuntimeException("throw")
複製代碼

解構聲明

解構聲明是建立多個變量與對象componentN 函數對應起來。例如在上面的數據類中

val (name, age) = KotlinDataClass.User("Lisa", 18)
複製代碼

NOTE: componentN() 函數須要用 operator 關鍵字標記,以容許在解構聲明中使用它們。它能夠用for-循環、

map-映射, 以及 lambda 表達式中。

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())

    //解構聲明和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)
        }
    }
}
複製代碼

解構聲明的好處, 如request 函數時要返回兩個東西時,用它爽爆了。由於編譯器始終會建立多個變量接收,效率並不比以前用對象的高。但實際上並不須要解析一個對象裏的大量變量,不然經過對象 .屬性獲取值。

val (name, age) = person //編譯器會生成以下兩句代碼
val name = person.component1()
val age = person.component2()
複製代碼

相等性

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

  • 引用相等(兩個引用指向同一對象)
  • 結構相等(用 equals() 檢查)

引用相等

引用相等由 ===(以及其否認形式 !==)操做判斷。a === b 當且僅當 ab 指向同一個對象時求值爲 true。

結構相等

結構相等由 ==(以及其否認形式 !=)操做判斷。按照慣例,像 a == b 這樣的表達式會翻譯成

a?.equals(b) ?: (b === null)
複製代碼

也就是說若是 a 不是 null 則調用 equals(Any?) 函數,不然(即 anull)檢查 b 是否與 null 引用相等。

請注意,當與 null 顯式比較時徹底不必優化你的代碼:a == null 會被自動轉換爲 a=== null

浮點數相等性

當相等性檢測的兩個操做數都是靜態已知的(可空或非空的)FloatDouble 類型時,該檢測遵循 IEEE 754 浮點數運算標準。

不然會使用不符合該標準的結構相等性檢測,這會致使 NaN 等於其自身,而 -0.0 不等於 0.0

異常

異常類

Kotlin 中全部異常類都是 Throwable 類的子孫類。 每一個異常都有消息、堆棧回溯信息和可選的緣由。

使用 throw-表達式來拋出異常:

throw MyException("Hi There!")
複製代碼

使用 try-表達式來捕獲異常:

try {
    // 一些代碼
}
catch (e: SomeException) {
    // 處理程序
}
finally {
    // 可選的 finally 塊
}
複製代碼

能夠有零到多個 catch 塊。finally 塊能夠省略。 可是 catchfinally 塊至少應該存在一個。

Try 是一個表達式

try 是一個表達式,即它能夠有一個返回值:

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

try-表達式的返回值是 try 塊中的最後一個表達式或者是(全部)catch 塊中的最後一個表達式。 finally 塊中的內容不會影響表達式的結果。

受檢的異常

Kotlin 沒有受檢的異常。這其中有不少緣由,但咱們會提供一個簡單的例子。

如下是 JDK 中 StringBuilder 類實現的一個示例接口:

Appendable append(CharSequence csq) throws IOException;
複製代碼

這個簽名是什麼意思? 它是說,每次我追加一個字符串到一些東西(一個 StringBuilder、某種日誌、一個控制檯等)上時我就必須捕獲那些 IOException。 爲何?由於它可能正在執行 IO 操做(Writer 也實現了 Appendable)…… 因此它致使這種代碼隨處可見的出現:

try {
    log.append(message)
}
catch (IOException e) {
    // 必需要安全
}
複製代碼

這並很差,參見《Effective Java》 第 65 條:不要忽略異常

Bruce Eckel 在《Java 是否須要受檢的異常?》(Does Java need Checked Exceptions?) 中指出:

經過一些小程序測試得出的結論是異常規範會同時提升開發者的生產力和代碼質量,可是大型軟件項目的經驗代表一個不一樣的結論——生產力下降、代碼質量不多或沒有提升。

其餘相關引證:

注意:throw 表達式的類型是特殊類型 Nothing。參見Nothing類型

反射

反射是這樣的一組語言和庫功能,它容許在運行時自省你的程序的結構。 Kotlin 讓語言中的函數和屬性作爲一等公民、並對其自省(即在運行時獲悉一個名稱或者一個屬性或函數的類型)與簡單地使用函數式或響應式風格緊密相關。

在 Java 平臺上,使用反射功能所需的運行時組件做爲單獨的 JAR 文件(kotlin-reflect.jar)分發。這樣作是爲了減小不使用反射功能的應用程序所需的運行時庫的大小。若是你須要使用反射,請確保該 .jar文件添加到項目的 classpath 中。

類引用

最基本的反射功能是獲取 Kotlin 類的運行時引用。要獲取對靜態已知的 Kotlin 類的引用,可使用 類字面值 語法:

val c = MyClass::class
複製代碼

該引用是 KClass 類型的值。

請注意,Kotlin 類引用與 Java 類引用不一樣。要得到 Java 類引用, 請在 KClass 實例上使用 .java 屬性。

綁定的類引用(自 1.1 起)

經過使用對象做爲接收者,能夠用相同的 ::class 語法獲取指定對象的類的引用:

val widget: Widget = ……
assert(widget is GoodWidget) { "Bad widget: ${widget::class.qualifiedName}" }
複製代碼

你能夠獲取對象的精確類的引用,例如 GoodWidgetBadWidget,儘管接收者表達式的類型是 Widget

函數引用

當咱們有一個命名函數聲明以下:

fun isOdd(x: Int) = x % 2 != 0
複製代碼

咱們能夠很容易地直接調用它(isOdd(5)),可是咱們也能夠把它做爲一個值傳遞。例如傳給另外一個函數。 爲此,咱們使用 :: 操做符:

val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd)) // 輸出 [1, 3]
複製代碼

這裏 ::isOdd 是函數類型 (Int) -> Boolean 的一個值。

當上下文中已知函數指望的類型時,:: 能夠用於重載函數。 例如:

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)
複製代碼

或者,你能夠經過將方法引用存儲在具備顯式指定類型的變量中來提供必要的上下文:

val predicate: (String) -> Boolean = ::isOdd   // 引用到 isOdd(x: String)
複製代碼

若是咱們須要使用類的成員函數或擴展函數,它須要是限定的。 例如 String::toCharArray 爲類型 String提供了一個擴展函數:String.() -> CharArray

示例:函數組合

考慮如下函數:

fun <A, B, C> compose(f: (B) -> C, g: (A) -> B): (A) -> C {
    return { x -> f(g(x)) }
}
複製代碼

它返回一個傳給它的兩個函數的組合:compose(f, g) = f(g(*))。 如今,你能夠將其應用於可調用引用:

fun length(s: String) = s.length

val oddLength = compose(::isOdd, ::length)
val strings = listOf("a", "ab", "abc")

println(strings.filter(oddLength)) // 輸出 "[a, abc]"
複製代碼

屬性引用

要把屬性做爲 Kotlin中 的一等對象來訪問,咱們也可使用 :: 運算符:

var x = 1

fun main(args: Array<String>) {
    println(::x.get()) // 輸出 "1"
    ::x.set(2)
    println(x)         // 輸出 "2"
}
複製代碼

表達式 ::x 求值爲 KProperty<Int> 類型的屬性對象,它容許咱們使用 get() 讀取它的值,或者使用 name 屬性來獲取屬性名。更多信息請參見關於 KProperty 類的文檔

對於可變屬性,例如 var y = 1::y 返回 KMutableProperty 類型的一個值, 該類型有一個 set() 方法。

屬性引用能夠用在不須要參數的函數處:

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"
}
複製代碼

對於擴展屬性:

val String.lastChar: Char
    get() = this[length - 1]

fun main(args: Array<String>) {
    println(String::lastChar.get("abc")) // 輸出 "c"
}
複製代碼

與 Java 反射的互操做性

在Java平臺上,標準庫包含反射類的擴展,它提供了與 Java 反射對象之間映射(參見 kotlin.reflect.jvm包)。 例如,要查找一個用做 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"
}
複製代碼

要得到對應於 Java 類的 Kotlin 類,請使用 .kotlin 擴展屬性:

fun getKClass(o: Any): KClass<Any> = o.javaClass.kotlin
複製代碼

構造函數引用

構造函數能夠像方法和屬性那樣引用。他們能夠用於期待這樣的函數類型對象的任何地方:它與該構造函數接受相同參數而且返回相應類型的對象。 經過使用 :: 操做符並添加類名來引用構造函數。考慮下面的函數, 它期待一個無參並返回 Foo 類型的函數參數:

class Foo

fun function(factory: () -> Foo) {
    val x: Foo = factory()
}
複製代碼

使用 ::Foo,類 Foo 的零參數構造函數,咱們能夠這樣簡單地調用它:

function(::Foo)
複製代碼

綁定的函數與屬性引用(自 1.1 起)

你能夠引用特定對象的實例方法。

val numberRegex = "\\d+".toRegex()
println(numberRegex.matches("29")) // 輸出「true」
 
val isNumber = numberRegex::matches
println(isNumber("29")) // 輸出「true」
複製代碼

取代直接調用方法 matches 的是咱們存儲其引用。 這樣的引用會綁定到其接收者上。 它能夠直接調用(如上例所示)或者用於任何期待一個函數類型表達式的時候:

val strings = listOf("abc", "124", "a70")
println(strings.filter(numberRegex::matches)) // 輸出「[124]」
複製代碼

比較綁定的類型和相應的未綁定類型的引用。 綁定的可調用引用有其接收者「附加」到其上,所以接收者的類型再也不是參數:

val isNumber: (CharSequence) -> Boolean = numberRegex::matches

val matches: (Regex, CharSequence) -> Boolean = Regex::matches
複製代碼

屬性引用也能夠綁定:

val prop = "abc"::length
println(prop.get())   // 輸出「3」
複製代碼

類型別名

類型別名爲現有類型提供替代名稱。 若是類型名稱太長,你能夠另外引入較短的名稱,並使用新的名稱替代原類型名。能夠爲函數類型提供另外的別名,也能夠爲內部類和嵌套類建立新名稱

類型別名不會引入新類型。 它們等效於相應的底層類型。 當你在代碼中添加 typealias Predicate<T> 並使用 Predicate<Int> 時,Kotlin 編譯器老是把它擴展爲 (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)
}
複製代碼
相關文章
相關標籤/搜索