Kotlin 知識點總結

什麼是Kotlin?

Kotlin 是 JVM 和 Android 的實用編程語言,結合了OO和功能特性,專一於互操做性安全性清晰度工具支持。html

做爲通用語言,Kotlin 能夠在Java工做的地方工做:服務器端應用程序移動應用程序(Android)桌面應用程序。它適用於全部主要的工具和服務,如java

  • IntelliJ IDEA,Android Studio 和 Eclipse
  • Maven,Gradle 和 Ant
  • Spring Boot
  • GitHub,Slack 甚至 Minecraft

Kotlin的關鍵重點之一是混合 Java + Kotlin 項目的互操做性和無縫支持,使採用更容易,從而減小了樣板代碼和更多的類型安全性。此外,Kotlin有一個普遍的標準庫,使平常任務輕鬆流暢,同時保持字節碼足跡低。固然,也能夠在Kotlin中使用任何Java庫。反之亦然。node

基本語法

定義包名

在源文件的開頭定義包名git

package my.demo
import java.util.*
//...
複製代碼

包名沒必要和文件夾路徑一致:源文件能夠放在任意位置。github

定義函數

定義一個函數接受兩個 int 型參數,返回值爲 int:算法

fun sum(a: Int , b: Int): Int {
    return a + b
}

fun main(args: Array<String>) {
    print("sum of 3 and 5 is ")
    println(sum(3, 5))
}
複製代碼

該函數只有一個表達式函數體以及一個自推導型的返回值:express

fun sum(a: Int, b: Int) = a + b

fun main(args: Array<String>) {
    println("sum of 19 and 23 is ${sum(19, 23)}")
}
複製代碼

返回一個沒有意義的值:編程

fun printSum(a: Int, b: Int): Unit {
    println("sum of $a and $b is ${a + b}")
}

fun main(args: Array<String>) {
    printSum(-1, 8)
}
複製代碼

Unit 的返回類型能夠省略:json

fun printSum(a: Int, b: Int) {
    println("sum of $a and $b is ${a + b}")
}

fun main(args: Array<String>) {
    printSum(-1, 8)
}
複製代碼

定義局部變量

聲明常量:c#

fun main(args: Array<String>) {
    val a: Int = 1  // 當即初始化
    val b = 2   // 推導出Int型
    val c: Int  // 當沒有初始化值時必須聲明類型
    c = 3       // 賦值
    println("a = $a, b = $b, c = $c")
}
複製代碼

變量:

fun main(args: Array<String>) {
    var x = 5 // 推導出Int類型
    x += 1
    println("x = $x")
}
複製代碼

註釋

與 java 和 javaScript 同樣,Kotlin 支持單行註釋和塊註釋。

// 單行註釋

/* 哈哈哈哈 這是塊註釋 */
複製代碼

與 java 不一樣的是 Kotlin 的 塊註釋能夠級聯。

使用字符串模板

fun main(args: Array<String>) {
    var a = 1
    // 使用變量名做爲模板:
    val s1 = "a is $a"

    a = 2
    // 使用表達式做爲模板:
    val s2 = "${s1.replace("is", "was")}, but now is $a"
    println(s2)
}
複製代碼

使用條件表達式

fun maxOf(a: Int, b: Int): Int {
    if (a > b) {
        return a
    } else {
        return b
    }
}

fun main(args: Array<String>) {
    println("max of 0 and 42 is ${maxOf(0, 42)}")
}
複製代碼

把if當表達式:

fun maxOf(a: Int, b: Int) = if (a > b) a else b

fun main(args: Array<String>) {
    println("max of 0 and 42 is ${maxOf(0, 42)}")
}
複製代碼

使用可空變量以及空值檢查

當空值可能出現時應該明確指出該引用可空。

下面的函數是當 str 中不包含整數時返回空:

fun parseInt(str : String): Int?{
	//...
}
複製代碼

使用一個返回可空值的函數:

fun parseInt(str: String): Int? {
    return str.toIntOrNull()
}

fun printProduct(arg1: String, arg2: String) {
    val x = parseInt(arg1)
    val y = parseInt(arg2)

    // 直接使用 x*y 會產生錯誤由於它們中有可能會有空值
    if (x != null && y != null) {
        // x 和 y 將會在空值檢測後自動轉換爲非空值
        println(x * y)
    }
    else {
        println("either '$arg1' or '$arg2' is not a number")
    }    
}


fun main(args: Array<String>) {
    printProduct("6", "7")
    printProduct("a", "7")
    printProduct("a", "b")
}
複製代碼

或者這樣

fun parseInt(str: String): Int? {
    return str.toIntOrNull()
}

fun printProduct(arg1: String, arg2: String) {
    val x = parseInt(arg1)
    val y = parseInt(arg2)

    // ...
    if (x == null) {
        println("Wrong number format in arg1: '${arg1}'")
        return
    }
    if (y == null) {
        println("Wrong number format in arg2: '${arg2}'")
        return
    }

    // x 和 y 將會在空值檢測後自動轉換爲非空值
    println(x * y)
}
複製代碼

使用值檢查並自動轉換

使用 is 操做符檢查一個表達式是不是某個類型的實例。若是對不可變的局部變量或屬性進行過了類型檢查,就沒有必要明確轉換:

fun getStringLength(obj: Any): Int? {
    if (obj is String) {
        // obj 將會在這個分支中自動轉換爲 String 類型
        return obj.length
    }

    // obj 在種類檢查外仍然是 Any 類型
    return null
}

fun main(args: Array<String>) {
    fun printLength(obj: Any) {
        println("'$obj' string length is ${getStringLength(obj) ?: "... err, not a string"} ")
    }
    printLength("Incomprehensibilities")
    printLength(1000)
    printLength(listOf(Any()))
}
複製代碼

或者這樣

fun getStringLength(obj: Any): Int? {
    if (obj !is String) return null

    // obj 將會在這個分支中自動轉換爲 String 類型
    return obj.length
}

fun main(args: Array<String>) {
    fun printLength(obj: Any) {
        println("'$obj' string length is ${getStringLength(obj) ?: "... err, not a string"} ")
    }
    printLength("Incomprehensibilities")
    printLength(1000)
    printLength(listOf(Any()))
}
複製代碼

甚至能夠這樣

fun getStringLength(obj: Any): Int? {
    // obj 將會在&&右邊自動轉換爲 String 類型
    if (obj is String && obj.length >= 0) {
        return obj.length
    }

    return null
}

fun main(args: Array<String>) {
    fun printLength(obj: Any) {
        println("'$obj' string length is ${getStringLength(obj) ?: "... err, is empty or not a string at all"} ")
    }
    printLength("Incomprehensibilities")
    printLength("")
    printLength(1000)
}
複製代碼

使用循環

fun main(args: Array<String>) {
    val items = listOf("apple", "banana", "kiwi")
    for (item in items) {
        println(item)
    }
}
複製代碼

或者

fun main(args: Array<String>) {
    val items = listOf("apple", "banana", "kiwi")
    for (index in items.indices) {
        println("item at $index is ${items[index]}")
    }
}
複製代碼

使用 while 循環

fun main(args: Array<String>) {
    val items = listOf("apple", "banana", "kiwi")
    var index = 0
    while (index < items.size) {
        println("item at $index is ${items[index]}")
        index++
    }
}
複製代碼

使用 when 表達式

fun describe(obj: Any): String =
when (obj) {
    1          -> "One"
    "Hello"    -> "Greeting"
    is Long    -> "Long"
    !is String -> "Not a string"
    else       -> "Unknown"
}

fun main(args: Array<String>) {
    println(describe(1))
    println(describe("Hello"))
    println(describe(1000L))
    println(describe(2))
    println(describe("other"))
}
複製代碼

使用 ranges

檢查 in 操做符檢查數值是否在某個範圍內:

fun main(args: Array<String>) {
    val x = 10
    val y = 9
    if (x in 1..y+1) {
        println("fits in range")
    }
}
複製代碼

檢查數值是否在範圍外:

fun main(args: Array<String>) {
    val list = listOf("a", "b", "c")

    if (-1 !in 0..list.lastIndex) {
        println("-1 is out of range")
    }
    if (list.size !in list.indices) {
        println("list size is out of valid list indices range too")
    }
}
複製代碼

在範圍內迭代:

fun main(args: Array<String>) {
    for (x in 1..5) {
        print(x)
    }
}
複製代碼

或者使用步進:

fun main(args: Array<String>) {
    for (x in 1..10 step 2) {
        print(x)
    }
    // 相似於java i--
    for (x in 9 downTo 0 step 3) {
        print(x)
    }
}
複製代碼

使用集合

對一個集合進行迭代:

fun main(args: Array<String>) {
    val items = listOf("apple", "banana", "kiwi")
    for (item in items) {
        println(item)
    }
}
複製代碼

使用 in 操做符檢查集合中是否包含某個對象

fun main(args: Array<String>) {
    val items = setOf("apple", "banana", "kiwi")
    when {
        "orange" in items -> println("juicy")
        "apple" in items -> println("apple is fine too")
    }
}
複製代碼

使用lambda表達式過濾和映射集合:

fun main(args: Array<String>) {
    val fruits = listOf("banana", "avocado", "apple", "kiwi")
    fruits
        .filter { it.startsWith("a") }
        .sortedBy { it }
        .map { it.toUpperCase() }
        .forEach { println(it) }
}
複製代碼

習慣用語

建立DTOs(POJOs/POCOs) 數據類

data class Customer(val name: String, val email: String)
複製代碼

函數默認值

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

過濾 list

val positives = list.filter { x -> x > 0 }
複製代碼

或者更短:

val positives = list.filter { it > 0 }
複製代碼

字符串插值

println("Name $name")
複製代碼

實例檢查

when (x) {
	is Foo ->  ...
	is Bar -> ...
	else -> ...
}
複製代碼

遍歷 map/list

for ((k, v) in map) {
	print("$k -> $v")
}
複製代碼

k,v 能夠隨便命名

使用 ranges

for (i in 1..100) { ... }  // 閉區間: 包括100
for (i in 1 until 100) { ... } // 半開區間: 不包括100
for (x in 2..10 step 2) { ... }
for (x in 10 downTo 1) { ... }
if (x in 1..10) { ... }
for (i in 1..100) { ... }
for (i in 2..10) { ... }
複製代碼

只讀 list

val list = listOf("a", "b", "c")
複製代碼

只讀map

val map = mapOf("a" to 1, "b" to 2, "c" to 3)
複製代碼

訪問 map

println(map["key"])
map["key"] = value
複製代碼

懶屬性(延遲加載)

val p: String by lazy {
    // 生成string的值
}
複製代碼

擴展函數

fun String.spcaceToCamelCase() { ... }
"Convert this to camelcase".spcaceToCamelCase()
複製代碼

建立單例模式

object Resource {
    val name = "Name"
}
複製代碼

若是不爲空則... 的簡寫

val files = File("Test").listFiles()
println(files?.size)
複製代碼

若是不爲空...不然... 的簡寫

val files = File("test").listFiles()
println(files?.size ?: "empty")
複製代碼

若是聲明爲空執行某操做

val data = ...
val email = data["email"] ?: throw IllegalStateException("Email is missing!")
複製代碼

若是不爲空執行某操做

val date = ...
data?.let{
    ...//若是不爲空執行該語句塊
}
複製代碼

返回 when 判斷

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

try-catch 表達式

fun test() {
    val result = try {
            count()
    }catch (e: ArithmeticException) {
            throw IllegaStateException(e)
    }
    //處理 result
}
複製代碼

if 表達式

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

方法使用生成器模式返回 Unit

fun arrOfMinusOnes(size: Int): IntArray{
    return IntArray(size).apply{ fill(-1) }
}
複製代碼

只有一個表達式的函數

fun theAnswer() = 42
複製代碼

與下面的語句是等效的

fun theAnswer(): Int {
	return 42
}
複製代碼

這個能夠和其它習慣用語組合成高效簡潔的代碼。譬如說 when 表達式:

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

利用 with 調用一個對象實例的多個方法

class Turtle {
    fun penDown()
    fun penUp()
    fun turn(degrees: Double)
    fun forward(pixels: Double)
}
val myTurtle = Turtle()
with(myTurtle) { // 畫一個100像素的正方形
    penDown()
    for(i in 1..4) {
        forward(100.0)
        turn(90.0)
    }
    penUp()
}
複製代碼

Java 7’s try with resources

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

須要泛型信息的泛型函數的方便形式

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

inline fun <reified T: Any> Gson.fromJson(json): T = this.fromJson(json, T::class.java)
複製代碼

產生一個可能爲空的布爾值

val b: Boolean? = ...
if (b == true) {
    ...
} else {
    // `b` 是false或者null
}
複製代碼

代碼風格

命名風格

若有疑惑,默認爲Java編碼約定,好比:

-- 使用駱駝命名法(在命名中避免下劃線)

-- 類型名稱首字母大寫

-- 方法和屬性首字母小寫

-- 縮進用四個空格

-- public 方法要寫說明文檔,這樣它就能夠出如今 Kotllin Doc 中

冒號

在冒號區分類型和父類型中要有空格,在實例和類型之間是沒有空格的:

interface Foo : Bar { fun foo(a: Int): T }

Lambdas

在 Lambdas 表達式中,大括號與表達式間要有空格,箭頭與參數和函數體間要有空格。儘量的把 lambda 放在括號外面傳入

list.filter { it > 10 }.map { element -> element * 2 }
複製代碼

在使用簡短而非嵌套的lambda中,建議使用it而不是顯式地聲明參數。在使用參數的嵌套lambda中,參數應該老是顯式聲明

類聲明格式

參數比較少的類能夠用一行表示:

class Person(id: Int, name: String)
複製代碼

具備較多的參數的類應該格式化成每一個構造函數的參數都位於與縮進的單獨行中。此外,結束括號應該在新行上。若是咱們使用繼承,那麼超類構造函數調用或實現的接口列表應該位於與括號相同的行中

class Person(
    id: Int,
    name: String,
    surname: String
) : Human(id, name) {
    // ...
}
複製代碼

對於多個接口,應該首先定位超類構造函數調用,而後每一個接口應該位於不一樣的行中

class Person(
    id: Int,
    name: String,
    surname: String
) : Human(id, name),
    KotlinMaker {
    // ...
}
複製代碼

構造函數參數可使用常規縮進或連續縮進(雙倍正常縮進)。

Unit

若是函數返回 Unit ,返回類型應該省略:

fun foo() { // ": Unit"被省略了
}
複製代碼

函數 vs 屬性

在某些狀況下,沒有參數的函數能夠與只讀屬性互換。儘管語義是類似的,可是有一些風格上的約定在何時更偏向於另外一個。

在下面的狀況下,更偏向於屬性而不是一個函數:

-- 不須要拋出異常
-- 擁有O(1)複雜度
-- 低消耗的計算(或首次運行結果會被緩存)
-- 返回與調用相同的結果

基本類型

在 Kotlin 中,全部變量的成員方法和屬性都是一個對象。一些類型是內建的,它們的實現是優化過的,但對用戶來講它們就像普通的類同樣。在這節中,咱們將會講到大多數的類型:數值,字符,布爾,以及數組。

數值

Kotlin 處理數值的方法和 java 很類似,但不是徹底同樣。好比,不存在隱式轉換數值的精度,而且在字面上有一些小小的不一樣。

Kotlin 提供了以下內建數值類型(和 java 很類似):

類型 字寬
Double 64
Float 32
Long 64
Int 32
Short 16
Byte 8

注意字符在 Kotlin 中不是數值類型:

字面值常量

主要是如下幾種字面值常量:

-- 數型: 123
-- 長整型要加大寫 L : 123L
-- 16進制:0x0f
-- 二進制:0b00001011

注意不支持8進制

Kotlin 也支持傳統的浮點數表示:

-- 默認 Doubles : 123.5 , 123.5e10
-- Floats 要添加 fF123.5f

表示

在 java 平臺上,數值被 JVM 虛擬機以字節碼的方式物理存儲的,除非咱們須要作可空標識(好比說 Int?) 或者涉及泛型。在後者中數值是裝箱的。

注意裝箱過的數值是不保留特徵的:

val a: Int = 10000
print (a === a ) //打印 'true'
val boxedA: Int? =a
val anotherBoxedA: Int? = a
print (boxedA === anotherBoxedA ) //注意這裏打印的是 'false'
複製代碼

另外一方面,它們是值相等的:

val a: Int = 10000
print(a == a) // Prints 'true'
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA == anotherBoxedA) // Prints 'true'
複製代碼

顯式轉換

因爲不一樣的表示,短類型不是長類型的子類型。若是是的話咱們就會碰到下面這樣的麻煩了

//這是些僞代碼,不能編譯的
val a: Int? =1 //一個裝箱過的 Int (java.lang.Integer)
val b: Long? = a // 一個隱式裝箱的 Long (java.lang.Long)
print( a == b )// 很驚訝吧&emsp;此次打印出的是 'false' 這是因爲 Long 類型的 equals() 只有和 Long 比較纔會相同
複製代碼

所以不止是恆等,有時候連等於都會悄悄丟失。

因此,短類型是不會隱式轉換爲長類型的。這意味着咱們必須顯式轉換才能把 Byte 賦值給 Int

val b: Byte = 1 // OK, literals are checked statically
val i: Int = b //ERROR
複製代碼

咱們能夠經過顯式轉換把數值類型提高

val i: Int = b.toInt() // 顯式轉換
複製代碼

每一個數值類型都支持下面的轉換:

toByte(): Byte

toShort(): Short

toInt(): Int

toLong(): Long

toFloat(): Float

toDouble(): Double

toChar(): Char

隱式轉換通常狀況下是不容易被發覺的,由於咱們可使用上下文推斷出類型,而且算術運算會爲合適的轉換進行重載,好比

val l = 1.toLong + 1 //Long + Int => Long
複製代碼

運算符

Kotlin支持標準的算術運算表達式,這些運算符被聲明爲相應類的成員。參看運算符重載。

至於位運算,Kotlin 並無提供特殊的操做符,只是提供了能夠叫中綴形式的方法,好比:

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

下面是所有的位運算操做符(只能夠用在 IntLong 類型):

shl(bits) – 帶符號左移 (至關於 Java's <<)
shr(bits) – 帶符號右移 (至關於 Java's' >>)
ushr(bits) – 無符號右移 (至關於 Java's >>>)
and(bits) – 按位與
or(bits) – 按位或
xor(bits) – 按位異或
inv(bits) – 按位翻轉

字符

字符類型用 Char 表示。不能直接當作數值來使用

fun check(c: Char) {
    if (c == 1) { //ERROR: 類型不匹配
    	//...
    }
}
複製代碼

字符是由單引號包裹的 '1',特殊的字符經過反斜槓\轉義,下面的字符序列支持轉義:\t,\b,\n,\r,',",\$。編碼任何其餘字符,使用 Unicode 轉義語法:\uFF00

咱們能夠將字符顯示的轉義爲Int數字:

fun decimalDigitValue(c: Char): Int {
    if (c !in '0'..'9') 
        throw IllegalArgumentException("Out of range")
    return c.toInt() - '0'.toInt() //顯示轉換爲數值類型
}
複製代碼

和數值類型同樣,須要一個可空引用時,字符會被裝箱。特性不會被裝箱保留。

布爾值

布爾值只有 true 或者 false

布爾值的內建操做包括

|| – lazy disjunction && – lazy conjunction

Array

Arrays在 Kotlin 中由 Array 類表示,有 getset 方法(經過運算符重載能夠由[]調用),以及 size 方法,以及一些經常使用的函數:

class Array<T> private () {
    fun size(): Int
    fun get(index: Int): T
    fun set(Index: Int, value: T): Unit
    fun iterator(): Iterator<T>
    //...
}
複製代碼

咱們能夠給庫函數 arrayOf() 傳遞每一項的值來建立Array,arrayOf(1, 2, 3) 建立了一個[1, 2, 3] 這樣的數組。也可使用庫函數 arrayOfNulls() 建立一個指定大小的空Array。

或者經過指定Array大小並提供一個迭代器

(原文Another option is to use a factory function that takes the array size and the function that can return the initial value of each array element given its index):

// 建立一個 Array<String> 內容爲 ["0", "1", "4", "9", "16"]
val asc = Array(5, {i -> (i * i).toString() })
複製代碼

像咱們上面提到的,[] 操做符表示調用 get() set() 函數

注意:和 java 不同,arrays 在 kotlin 中是不可變的。這意味這 kotlin 不容許咱們把 Array<String> 轉爲 Array<Any> ,這樣就阻止了可能的運行時錯誤(但你可使用 Array<outAny> , 參看 Type Projections)

Kotlin 有專門的類來表示原始類型從而避免過分裝箱: ByteArray, ShortArray, IntArray 等等。這些類與 Array 沒有繼承關係,但它們有同樣的方法與屬性。每一個都有對應的庫函數:

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

字符串

字符串是由 String 表示的。字符串是不變的。字符串的元素能夠經過索引操做讀取: s[i] 。字符串能夠用 for 循環迭代:

for (c in str) {
    println(c)
}
複製代碼

Kotlin 有兩種類型的 String :一種是能夠帶分割符的,一種是能夠包含新行以及任意文本的。帶分割符的 String 很像 java 的 String:

val s = "Hello World!\n"
複製代碼

整行String 是由三個引號包裹的("""),不能夠包含分割符但能夠包含其它字符:

val text = """ for (c in "foo") print(c) """
複製代碼

注意:字符串比較
==表示比較內容,至關於java的equals
===表示比較對象是否相同

模板

字符串能夠包含模板表達式。一個模板表達式由一個 $ 開始幷包含另外一個簡單的名稱:

val i = 10
val s = "i = $i" // 識別爲 "i = 10"
複製代碼

或者是一個帶大括號的表達式:

val s = "abc"
val str = "$s.length is ${s.length}" //識別爲 "abc.length is 3"
複製代碼

Java Style 類型轉換(as)

var sub: SubClass = parent as SubClass
複製代碼

相似於 java 的類型轉換,失敗則拋異常。

安全類型轉換

var sub: SubClass? = parent as? SubClass
複製代碼

若是轉換失敗返回null,不拋異常。

智能類型轉換

  • if(parent is SubClass) parent.<子類的成員>

  • 編譯器儘量的推導類型,遠離無用的類型轉換

  • if(nullable != null) nullable.length

一個源文件以包聲明開始:

package foo.bar

fun bza() {}

class Goo {}

//...
複製代碼

源文件的全部內容(好比類和函數)都被包聲明包括。所以在上面的例子中, bza() 的全名應該是 foo.bar.bzaGoo 的全名是 foo.bar.Goo

若是沒有指定包名,那這個文件的內容就從屬於沒有名字的 default 包。

Imports

除了模塊中默認導入的包,每一個文件均可以有它本身的導入指令。導入語法的聲明在grammar中描述。

咱們能夠導入一個單獨的名字,好比下面這樣:

import foo.Bar //Bar 如今能夠不用條件就可使用
複製代碼

或者範圍內的全部可用的內容 (包,類,對象,等等):

import foo.*/ /foo 中的全部均可以使用
複製代碼

若是命名有衝突,咱們可使用 as 關鍵字局部重命名解決衝突

import foo.Bar // Bar 可使用
import bar.Bar as bBar // bBar 表明 'bar.Bar'
複製代碼

可見性和包嵌套

若是最頂的聲明標註爲 private , 那麼它是本身對應包私有 (參看 Visibility Modifiers)。若是包內有私有的屬性或方法,那它對全部的子包是可見的。

注意包外的的成員是默認不導入的,好比在導入 foo.bar 後咱們不能得到 foo 的成員

流程控制

if 表達式

在 Kotlin 中,if 是表達式,好比它能夠返回一個值。是除了condition ? then : else)以外的惟一一個三元表達式

//傳統用法
var max = a
if (a < b)
    max = b

//帶 else 
var max: Int
if (a > b)
    max = a
else
    max = b

//做爲表達式
val max = if (a > b) a else b
複製代碼

if 分支能夠做爲塊,最後一個表達是是該塊的值:

val max = if (a > b){
    print("Choose a")
    a
}
else{
    print("Choose b")
    b
}
複製代碼

若是 if 表達式只有一個分支,或者分支的結果是 Unit , 它的值就是 Unit 。

When 表達式

when 取代了 C 風格語言的 switch 。最簡單的用法像下面這樣

when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> { //Note the block
    	print("x is neither 1 nor 2")
    }
}
複製代碼

when會對全部的分支進行檢查直到有一個條件知足。when 能夠用作表達式或聲明。若是用做表達式的話,那麼知足條件的分支就是總表達式。若是用作聲明,那麼分支的值會被忽略。(像 if 表達式同樣,每一個分支是一個語句塊,並且它的值就是最後一個表達式的值)

在其它分支都不匹配的時候默認匹配 else 分支。若是把 when 作爲表達式的話 else 分支是強制的,除非編譯器能夠提供全部覆蓋全部可能的分支條件。

若是有分支能夠用一樣的方式處理的話,分支條件能夠連在一塊兒:

when (x) {
    0,1 -> print("x == 0 or x == 1")
    else -> print("otherwise")
}
複製代碼

能夠用任意表達式做爲分支的條件

when (x) {
    parseInt(s) -> print("s encode x")
    else -> print("s does not encode x")
}
複製代碼

甚至能夠用 in 或者 !in 檢查值是否值在一個集合中:

when (x) {
    in 1..10 -> print("x is in the range")
    in validNumbers -> print("x is valid")
    !in 10..20 -> print("x is outside the range")
    else -> print("none of the above")
}
複製代碼

也能夠用 is 或者 !is 來判斷值是不是某個類型。注意,因爲 smart casts ,你能夠不用另外的檢查就可使用相應的屬性或方法。

val hasPrefix = when (x) {
    is String -> x.startsWith("prefix")
    else -> false
}
複製代碼

when 也能夠用來代替 if-else if 。若是沒有任何參數提供,那麼分支的條件就是簡單的布爾表達式,當條件爲真時執行相應的分支:

when {
    x.isOdd() -> print("x is odd")
    x.isEven() -> print("x is even")
    else -> print("x is funny")
}
複製代碼

for 循環

for 循環經過任何提供的迭代器進行迭代。語法是下面這樣的:

for (item in collection)
    print(item)
複製代碼

內容能夠是一個語句塊

for (item: Int in ints){
    //...
}
複製代碼

像以前提到的, for 能夠對任何提供的迭代器進行迭代,好比:

has an instance- or extension-function iterator(), whose return type

has an instance- or extension-function next(), and

has an instance- or extension-function hasNext() that returns Boolean.

若是你想經過 list 或者 array 的索引進行迭代,你能夠這樣作:

for (i in array.indices)
    print(array[i])
複製代碼

在沒有其它對象建立的時候 "iteration through a range " 會被自動編譯成最優的實現。

while 循環

while 和 do...while 像往常那樣

while (x > 0) {
    x--
}

do {
    val y = retrieveData()
} while (y != null) // y 在這是可見的
複製代碼

參看[while 語法](has an instance- or extension-function hasNext() that returns Boolean.)

在循環中使用 break 和 continue

kotlin 支持傳統的 break 和 continue 操做符。參看返回和跳轉

返回與跳轉

Kotlin 有三種機構跳轉操做符

return break 結束最近的閉合循環
continue 跳到最近的閉合循環的下一次循環

break 和 continue 標籤

在 Kotlin 中表達式能夠添加標籤。標籤經過 @ 結尾來表示,好比:abc@fooBar@ 都是有效的(參看語法)。使用標籤語法只需像這樣:

loop@ for (i in 1..100){
    //...
}
複製代碼

如今咱們能夠用標籤實現 break 或者 continue 的快速跳轉:

loop@ for (i in 1..100) {
    for (j in i..100) {
    	if (...)
            break@loop
    }
}
複製代碼

break 是跳轉標籤後面的表達式,continue 是跳轉到循環的下一次迭代。

返回到標籤處在字面函數,局部函數,以及對象表達式中,函數能夠在 Kotlin 中被包裹。return 容許咱們返回到外層函數。最重要的例子就是從字面函數中返回,還記得咱們以前的寫法嗎:

fun foo() {
    ints.forEach {
    	if (it  == 0) return
    	print(it)
    }
}
複製代碼

return 表達式返回到最近的閉合函數,好比 foo (注意這樣非局部返回僅僅能夠在內聯函數中使用)。若是咱們須要從一個字面函數返回可使用標籤修飾 return :

fun foo() {
    ints.forEach lit@ {
    	if (it ==0) return@lit
    	print(it)
    }
}
複製代碼

如今它僅僅從字面函數中返回。常常用一種更方便的含蓄的標籤:好比用和傳入的 lambda 表達式名字相同的標籤。

fun foo() {
    ints.forEach {
    	if (it == 0) return@forEach
    	print(it)
    }
}
複製代碼

另外,咱們能夠用函數表達式替代字面函數。在函數表達式中使用 return 語句能夠從函數表達式中返回。

fun foo() {
    ints.forEach(fun(value: Int){
    	if (value == 0) return
    	print(value)
    })
}
複製代碼

當返回一個值時,解析器給了一個參考,好比(原文When returning a value, the parser gives preference to the qualified return, i.e.):

return@a 1
複製代碼

命名函數自動定義標籤:

foo outer() {
    foo inner() {
    	return@outer
    }
}
複製代碼

類和繼承

在 Kotlin 中類用 class 聲明:

class Invoice {
}
複製代碼

類的聲明包含類名,類頭(指定類型參數,主構造函數等等),以及類主體,用大括號包裹。類頭和類體是可選的;若是沒有類體能夠省略大括號。

class Empty
複製代碼

構造函數

在 Kotlin 中類能夠有一個主構造函數以及多個二級構造函數。主構造函數是類頭的一部分:跟在類名後面(能夠有可選的類型參數)。

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

若是主構造函數沒有註解或可見性說明,則 constructor 關鍵字是能夠省略:

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

主構造函數不能包含任意代碼。初始化代碼能夠放在以 init 作前綴的初始化塊內

class Customer(name: String) {
    init {
    	logger,info("Customer initialized with value ${name}")
    }
}
複製代碼

注意主構造函數的參數能夠用在初始化塊內,也能夠用在類的屬性初始化聲明處:

class Customer(name: String) {
    val customerKry = name.toUpperCase()
}
複製代碼

事實上,聲明屬性並在主構造函數中初始化,在 Kotlin 中有更簡單的語法:

class Person(val firstName: String, val lastName: String, var age: Int) {
}
複製代碼

就像普通的屬性,在主構造函數中的屬性能夠是可變的(var)或只讀的(val)。

若是構造函數有註解或可見性聲明,則 constructor 關鍵字是不可少的,而且可見性應該在前:

class Customer public @inject constructor (name: String) {...}
複製代碼

二級構造函數

類也能夠有二級構造函數,須要加前綴 constructor:

class Person {
    constructor(parent: Person) {
    	parent.children.add(this)
    }
}
複製代碼

若是類有主構造函數,每一個二級構造函數都要直接或間接經過另外一個二級構造函數代理主構造函數。在同一個類中代理另外一個構造函數使用 this 關鍵字:

class Person(val name: String) {
    constructor (name: String, paret: Person) : this(name) {
    	parent.children.add(this)
    }
}
複製代碼

若是一個非抽象類沒有聲明構造函數(主構造函數或二級構造函數),它會產生一個沒有參數的構造函數。該構造函數的可見性是 public 。若是你不想你的類有公共的構造函數,你就得聲明一個擁有非默承認見性的空主構造函數:

class DontCreateMe private constructor () {
}
複製代碼

注意:在 JVM 虛擬機中,若是主構造函數的全部參數都有默認值,編譯器會生成一個附加的無參的構造函數,這個構造函數會直接使用默認值。這使得 Kotlin 能夠更簡單的使用像 Jackson 或者 JPA 這樣使用無參構造函數來建立類實例的庫。

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

建立類的實例

咱們能夠像使用普通函數那樣使用構造函數建立類實例:

val invoice = Invoice()
val customer = Customer("Joe Smith")
複製代碼

注意 Kotlin 沒有 new 關鍵字。

建立嵌套類、內部類或匿名類的實例參見嵌套類

類成員

類能夠包含:

-- 構造函數和初始化代碼塊

-- 函數

-- 屬性 

-- 內部類

-- 對象聲明

繼承

Kotlin 中全部的類都有共同的父類 Any ,它是一個沒有父類聲明的類的默認父類:

class Example //&emsp;隱式繼承於 Any
複製代碼

Any 不是 java.lang.Object;事實上它除了 equals(),hashCode()以及toString()外沒有任何成員了。參看[Java interoperability]( Java interoperability)瞭解更多詳情。

聲明一個明確的父類,須要在類頭後加冒號再加父類:

open class Base(p: Int)

class Derived(p: Int) : Base(p)
複製代碼

若是類有主構造函數,則基類能夠並且是必須在主構造函數中使用參數當即初始化。

若是類沒有主構造函數,則必須在每個構造函數中用 super 關鍵字初始化基類,或者在代理另外一個構造函數作這件事。注意在這種情形中不一樣的二級構造函數能夠調用基類不一樣的構造方法:

class MyView : View {
    constructor(ctx: Context) : super(ctx) {
    }
    constructor(ctx: Context, attrs: AttributeSet) : super(ctx,attrs) {
    }
}
複製代碼

open註解與java中的final相反:它容許別的類繼承這個類。默認情形下,kotlin 中全部的類都是 final ,對應 Effective Java :Design and document for inheritance or else prohibit it.

複寫方法

像以前提到的,咱們在 kotlin 中堅持作明確的事。不像 java ,kotlin 須要把能夠複寫的成員都明確註解出來,而且重寫它們:

open class Base {
    open fun v() {}
    fun nv() {}
}

class Derived() : Base() {
    override fun v() {}
}
複製代碼

對於 Derived.v() 來講override註解是必須的。若是沒有加的話,編譯器會提示。若是沒有open註解,像 Base.nv() ,在子類中聲明一個一樣的函數是不合法的,要麼加override要麼不要複寫。在 final 類(就是沒有open註解的類)中,open 類型的成員是不容許的。

標記爲override的成員是open的,它能夠在子類中被複寫。若是你不想被重寫就要加 final:

open class AnotherDerived() : Base() {
    final override fun v() {}
}
複製代碼

等等!我如今怎麼hack個人庫?!

有個問題就是如何複寫子類中那些做者不想被重寫的類,下面介紹一些使人討厭的方案。

咱們認爲這是很差的,緣由以下:

最好的實踐建議你不該給作這些 hack

人們能夠用其餘的語言成功作到相似的事情

若是你真的想 hack 那麼你能夠在 java 中寫好 hack 方案,而後在 kotlin 中調用 (參看java調用),專業的構架能夠很好的作到這一點

複寫屬性

複寫屬性與複寫方法相似,在一個父類上聲明的屬性在子類上被從新聲明,必須添加override,而且它們必須具備兼容的類型。每一個被聲明的屬性均可以被一個帶有初始化器的屬性或帶有getter方法的屬性覆蓋

open class Foo {
    open val x: Int get { ... }
}

class Bar1 : Foo() {
    override val x: Int = ...
}
複製代碼

您還可使用var屬性覆蓋一個val屬性,但反之則不容許。這是容許的,由於val屬性本質上聲明瞭一個getter方法,並將其重寫爲var,另外在派生類中聲明瞭setter方法。

注意,能夠在主構造函數中使用override關鍵字做爲屬性聲明的一部分。

interface Foo {
    val count: Int
}

class Bar1(override val count: Int) : Foo

class Bar2 : Foo {
    override var count: Int = 0
}
複製代碼

複寫規則

在 kotlin 中,實現繼承一般遵循以下規則:若是一個類從它的直接父類繼承了同一個成員的多個實現,那麼它必須複寫這個成員而且提供本身的實現(或許只是直接用了繼承來的實現)。爲表示使用父類中提供的方法咱們用 super<Base>表示:

open class A {
    open fun f () { print("A") }
    fun a() { print("a") }
}

interface B {
    fun f() { print("B") } // 接口的成員變量默認是 open 的
    fun b() { print("b") }
}

class C() : A() , B {
    // 編譯器會要求複寫f()
    override fun f() {
    	super<A>.f() // 調用 A.f()
    	super<B>.f() // 調用 B.f()
    }
}
複製代碼

能夠同時從 A 和 B 中繼承方法,並且 C 繼承 a() 或 b() 的實現沒有任何問題,由於它們都只有一個實現。可是 f() 有倆個實現,所以咱們在 C 中必須複寫 f() 而且提供本身的實現來消除歧義。

抽象類

一個類或一些成員可能被聲明成 abstract 。一個抽象方法在它的類中沒有實現方法。記住咱們不用給一個抽象類或函數添加 open 註解,它默認是帶着的。

咱們能夠用一個抽象成員去複寫一個帶 open 註解的非抽象方法。

open class Base {
	open fun f() {}
}

abstract class Derived : Base() {
	override abstract fun f()
}
複製代碼

伴隨對象

在 kotlin 中不像 java 或者 C# 它沒有靜態方法。在大多數情形下,咱們建議只用包級別的函數。

若是你要寫一個沒有實例類就能夠調用的方法,但須要訪問到類內部(好比說一個工廠方法),你能夠把它寫成它所在類的一個成員(you can write it as a member of an object declaration inside that class)

更高效的方法是,你能夠在你的類中聲明一個伴隨對象,這樣你就能夠像 java/c# 那樣把它當作靜態方法調用,只須要它的類名作一個識別就行了

密封類

密封類用於表明嚴格的類結構,值只能是有限集合中的某中類型,不能夠是任何其它類型。這就至關於一個枚舉類的擴展:枚舉值集合的類型是嚴格限制的,但每一個枚舉常量只有一個實例,而密封類的子類能夠有包含不一樣狀態的多個實例。

聲明密封類須要在 class 前加一個 sealed 修飾符。密封類能夠有子類但必須所有嵌套在密封類聲明內部、

sealed class Expr {
    class Const(val number: Double) : Expr()
    class Sum(val e1: Expr, val e2: Expr) : Expr()
    object NotANumber : Expr()
}
複製代碼

注意密封類子類的擴展能夠在任何地方,沒必要在密封類聲明內部進行。

使用密封類的最主要的的好處體如今你使用 when 表達式。能夠確保聲明能夠覆蓋到全部的情形,不須要再使用 else 情形。

fun eval(expr: Expr): Double = when(expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NaN
    // the `else` clause is not required because we've covered all the cases
}
複製代碼

屬性和字段

聲明屬性

在類中,使用關鍵字 varval 聲明屬性。

只須要將成員變量定義成一個變量,默認是 public 的。編譯器會自動生成 gettersetter 方法。下面的屬性編譯器默認添加了 gettersetter 方法。

public class Address {
    public var name: String = ...
    public var street: String = ...
    public var city: String = ...
    public var state: String? = ...
    public var zip: String = ...
}
複製代碼

屬性的使用

要使用一個屬性,只須要使用名稱引用便可:

funcopyAddress(address: Address): Address {

    val result = Address() // there's no 'new'keyword in Kotlin
    result.name = address.name // accessors arecalled
    result.street = address.street
    // ...
    return result
}
複製代碼

注:上面對屬性的訪問,並非像Java裏面同樣,直接訪問屬性的自己,而是默認調用了gettersetter 方法。

Getters and Setters

完整的屬性聲明以下:

var<propertyName>: <PropertyType> [= <property_initializer>]

[<getter>]

[<setter>]
複製代碼

初始器(initializer)、gettersetter 都是可選的。 屬性類型(PropertyType)若是能夠從初始器或者父類中推導出來,也能夠省略。

var 是容許有 gettersetter 方法,若是變量是val聲明的,它相似於Java中的final,因此若是以val聲明就不容許有setter方法。

val isEmpty:Boolean

get() = this.size == 0

var stringRepresentation: String

get() = this.toString()

set(value) {
    setDataFromString(value) // parses thestring and assigns values to other properties
}
複製代碼

實例:

class Person (name: String) {
    var name: String = name
    get() = field.toUpperCase()
    set(value) {
        field = value
    }
}

//

fun main(args:Array<String>) {
    var customer: Person = Person("xiaomin")
    println(customer.name)   // XIAOMIN
    customer.name = "lei"
    println(customer.name)   // LEI
}
複製代碼

另外,對於屬性,若是你想改變訪問的可見性或者是對其進行註解,可是又不想改變它的默認實現,那麼你就能夠定義set和get但不進行實現。

var setterVisibility: String = "abc" // Initializer required, not a nullable type
private set // the setter is private and hasthe default implementation
var setterWithAnnotation: Any?
@Injectset // annotate the setter with Inject
複製代碼

支持(反向)域(Backing Fields)

如在上面例子中定義的Person類裏面,屬性的getset方法裏面使用了一個field,它是一個自動的返回字段,表明的就是該屬性。

field只有在訪問屬性的時候纔會產生,其餘時候是不會產生的。

var name: String = name

get() = field.toUpperCase()

set(value) {
    field = value
}
複製代碼

支持(反向)屬性(Backing Properties)

若是Backing Fields不適用的話,其實能夠直接使用返回屬性就能夠了。

private var _table:Map<String, Int>? = null

public val table: Map<String, Int>

get() {
    if (_table == null)
        _table = HashMap() // Type parameters areinferred
    return _table ?: throwAssertionError("Set to null by another thread")
}
複製代碼

延遲初始化屬性

對於非空類型的屬性是必須初始化的。若是咱們但願延遲進行初始化,就可使用lateinit關鍵字了。

lateinit只能在不可null的對象上使用,必須爲var,不能爲primitives(Int、Float之類)。

public class MyTest{
    lateinit var subject: TestSubject

    @SetUp fun setup() {
        subject = TestSubject()
    }

    @Test fun test() {
        subject.method()  // dereference directly
    }
}
複製代碼

接口

Kotlin 的接口很像 java 8。它們均可以包含抽象方法,以及方法的實現。和抽象類不一樣的是,接口不能保存狀態。能夠有屬性但必須是抽象的,或者提供訪問器的實現。

接口用關鍵字 interface 來定義:

interface MyInterface {
    fun bar()
    fun foo() {
    	//函數體是可選的
    }
}
複製代碼

接口的實現

一個類或對象能夠實現一個或多個接口

class Child : MyInterface {
    fun bar () {
    	//函數體
    }
}
複製代碼

接口中的屬性

能夠在接口中申明屬性。接口中的屬性要麼是抽象的,要麼提供訪問器的實現。接口屬性不能夠有後備字段。並且訪問器不能夠引用它們。

interface MyInterface {
    val property: Int // abstract
    
    val propertyWithImplementation: String
    	get() = "foo"
    
    fun foo() { 
    	print(property)
    } 
}

class Child : MyInterface { 
    override val property: Int = 29
}
複製代碼

解決重寫衝突

當咱們在父類中聲明瞭許多類型,有可能出現一個方法的多種實現。好比:

interface A {
    fun foo() { print("A") }
    fun bar()
}

interface B {
    fun foo() { print("B") }
    fun bar() { print("bar") }
}

class C : A {
    override fun bar() { print("bar") }
}

class D : A, B {
    override fun foo() {
        super<A>.foo()
        super<B>.foo()
    }
}
複製代碼

A B 接口都有聲明瞭 foo() bar() 函數。它們都實現了 foo() 方法,但只有 B 實現了 bar() ,bar() 在 A 中並無聲明它是抽象的,這是由於在接口中若是函數沒有函數體,那麼默認是抽像的。如今,若是咱們從 A 中派生一個 C 實體類,顯然咱們須要重寫 bar() ,並實現它。而咱們從 A 和 B 派生一個 D ,咱們不用重寫 bar() 方法,由於咱們的一個繼承中有一個已經實現了它。但咱們繼承了兩個 foo() 的實現,所以編譯器不知道應該選哪一個,並強制咱們重寫 foo() 而且明確指出咱們想怎麼實現。

可見性修飾詞

類,對象,接口,構造函數,屬性以及它們的 setter 方法均可以有可見性修飾詞。( getter 方法做爲屬性時都是可見性)。在 Kotlin 中有四種修飾詞:private,protected,internal,以及 public 。默認的修飾符是 public。 下面將解釋不一樣類型的聲明做用域。

函數,屬性和類,對象和接口能夠在 "top-level" 聲明:

package foo
fun baz() {}
class bar {}
複製代碼

若是沒有指明任何可見性修飾詞,默認使用 public ,這意味着你的聲明在任何地方均可見;

若是你聲明爲 private ,則只在包含聲明的文件中可見;

若是用 internal 聲明,則在同一模塊中的任何地方可見;

protected 在 "top-level" 中不可使用

例子:

package foo

private fun foo() {} // visible inside example.kt

public var bar: Int = 5 // property is visible everywhere 

private set // setter is visible only in example.kt

internal val baz = 6 // visible inside the same module
複製代碼

類和接口

當在類中聲明時:

private 只在該類(以及它的成員)中可見

protectedprivate 同樣,但在子類中也可見

internal 在本模塊的全部能夠訪問到聲明區域的都可以訪問該類的全部 internal 成員 ( internal — any client inside this module who sees the declaring class sees its internal members;)

public 任何地方可見 (public — any client who sees the declaring class sees its public members.)

java 使用者注意:外部類不能夠訪問內部類的 private 成員。

例子:

open class Outer {
    private val a = 1
    protected val b = 2
    internal val c = 3
    val d = 4 // public by default
    protected class Nested { 
    	public val e: Int = 5
    } 
}
class Subclass : Outer() {
    // a is not visible
    // b, c and d are visible // Nested and e are visible
}
class Unrelated(o: Outer) {
    // o.a, o.b are not visible
    // o.c and o.d are visible (same module)
    // Outer.Nested is not visible, and Nested::e is not visible either
}
複製代碼

構造函數

經過下面的語法來指定主構造函數(必須顯示的使用 constructor 關鍵字)的可見性:

class C private constructor(a: Int) { ... }
複製代碼

這裏構造函數是 private 。全部的構造函數默認是 public ,實際上只要類是可見的它們就是可見的 (注意 internal 類型的類中的 public 屬性只能在同一個模塊內才能夠訪問)

局部聲明

局部變量,函數和類是不容許使用修飾詞的

模塊

internal 修飾符是指成員的可見性是隻在同一個模塊中才可見的。模塊在 Kotlin 中就是一系列的 Kotlin 文件編譯在一塊兒:

— an IntelliJ IDEA module;

— a Maven or Gradle project;

— a set of files compiled with one invocation of the Ant task.

擴展

與 C# 和 Gosu 相似, Kotlin 也提供了一種,能夠在不繼承父類,也不使用相似裝飾器這樣的設計模式的狀況下對指定類進行擴展。咱們能夠經過一種叫作擴展的特殊聲明來實現他。Kotlin 支持函數擴展和屬性擴展。

函數擴展

爲了聲明一個函數擴展,咱們須要在函數前加一個接收者類型做爲前綴。下面咱們會爲 MutableList 添加一個 swap 函數:

fun MutableList<Int>.swap(x: Int, y: Int) {
    val temp = this[x] // this 對應 list
    this[x] = this[y]
    this[y] = tmp
}
複製代碼

在擴展函數中的 this 關鍵字對應接收者對象。如今咱們能夠在任何 MutableList 實例中使用這個函數了:

val l = mutableListOf(1, 2, 3)
l.swap(0, 2)// 在 `swap()` 函數中 `this` 持有的值是 `l`
複製代碼

固然,這個函數對任意的 MutableList<T> 都是適用的,並且咱們能夠把它變的通用:

fun <T> MutableList<T>.swap(x: Int, y: Int) {
  val tmp = this[x] // 'this' corresponds to the list
  this[x] = this[y]
  this[y] = tmp
}
複製代碼

咱們在函數名前聲明瞭通用類型,從而使它能夠接受任何參數。參看泛型函數。

擴展是被靜態解析的

擴展實際上並無修改它所擴展的類。定義一個擴展,你並無在類中插入一個新的成員,只是讓這個類的實例對象可以經過.調用新的函數。

須要強調的是擴展函數是靜態分發的,舉個例子,它們並非接受者類型的虛擬方法。這意味着擴展函數的調用時由發起函數調用的表達式的類型決定的,而不是在運行時動態得到的表達式的類型決定。好比

open class C 

class D: C()

fun C.foo() = "c" 

fun D.foo() = "d"

fun printFoo(c: C) { 
    println(c.foo())
} 

printFoo(D())
複製代碼

這個例子會輸出 c,由於這裏擴展函數的調用決定於聲明的參數 c 的類型,也就是 C。

若是有同名同參數的成員函數和擴展函數,調用的時候必然會使用成員函數,好比:

class C {
    fun foo() { println("member") }
}
fun C.foo() { println("extension") }
複製代碼

當咱們對C的實例c調用c.foo()的時候,他會輸出"member",而不是"extension"

但你能夠用不一樣的函數簽名經過擴展函數的方式重載函數的成員函數,好比下面這樣:

class C {
    fun foo() { println("number") }
}

fun C.foo(i:Int) { println("extention") }
複製代碼

C().foo(1) 的調用會打印 「extentions」。

可空的接收者

注意擴展可使用空接收者類型進行定義。這樣的擴展使得,即便是一個空對象仍然能夠調用該擴展,而後在擴展的內部進行 this == null 的判斷。這樣你就能夠在 Kotlin 中任意調用 toString() 方法而不進行空指針檢查:空指針檢查延後到擴展函數中完成。

fun Any?.toString(): String {
    if (this == null) return "null"
    // 在空檢查以後,`this` 被自動轉爲非空類型,所以 toString() 能夠被解析到任何類的成員函數中
    return toString()
}
複製代碼

屬性擴展

和函數相似, Kotlin 也支持屬性擴展:

val <T> List<T>.lastIndex:  Int
    get() = size-1
複製代碼

注意,因爲擴展並不會真正給類添加了成員屬性,所以也沒有辦法讓擴展屬性擁有一個備份字段。這也是爲何初始化函數不容許有擴展屬性。擴展屬性只可以經過明確提供 getter 和 setter方法來進行定義。

例子:

val Foo.bar = 1 //error: initializers are not allowed for extension properties
複製代碼

伴隨對象擴展

若是一個對象定義了伴隨對象,你也能夠給伴隨對象添加擴展函數或擴展屬性:

class MyClass {
    companion object {} 
}
fun MyClass.Companion.foo(){

}
複製代碼

和普通伴隨對象的成員同樣,它們能夠只用類的名字就調用:

MyClass.foo()
複製代碼

擴展的域

大多數時候咱們在 top level 定義擴展,就在包下面直接定義:

package foo.bar
fun Baz.goo() { ... }
複製代碼

爲了在除聲明的包外使用這個擴展,咱們須要在 import 時導入:

package com.example,usage

import foo.bar.goo // 導入全部名字叫 "goo" 的擴展
				
					// 或者

import foo.bar.* // 導入foo.bar包下得全部數據

fun usage(baz: Baz) {
    baz.goo()
}
複製代碼

動機

在 java 中,咱們一般使用一系列名字爲 "*Utils" 的類: FileUtils,StringUtils等等。頗有名的 java.util.Collections 也是其中一員的,但咱們不得不像下面這樣使用他們:

//java
Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list))
複製代碼

因爲這些類名老是不變的。咱們可使用靜態導入並這樣使用:

swap(list, binarySearch(list, max(otherList)), max(list))
複製代碼

這樣就好不少了,但這樣咱們就只能從 IDE 自動完成代碼那裏得到不多或得不到幫助信息。若是咱們能夠像下面這樣那麼就好多了

list.swap(list.binarySearch(otherList.max()), list.max())
複製代碼

但咱們又不想在 List 類中實現全部可能的方法。這就是擴展帶來的好處。

數據類

咱們常常建立一個只保存數據的類。在這樣的類中一些函數只是機械的對它們持有的數據進行一些推導。在 kotlin 中這樣的類稱之爲 data 類,用 data 標註:

data class User(val name: String, val age: Int)
複製代碼

編譯器會自動根據主構造函數中聲明的全部屬性添加以下方法:

equals()/hashCode 函數

toString 格式是 "User(name=john, age=42)"

[compontN()functions] (kotlinlang.org/docs/refere…) 對應按聲明順序出現的全部屬性

copy() 函數

若是在類中明確聲明或從基類繼承了這些方法,編譯器不會自動生成。

爲確保這些生成代碼的一致性,並實現有意義的行爲,數據類要知足下面的要求:

注意若是構造函數參數中沒有 val 或者 var ,就不會在這些函數中出現;

主構造函數應該至少有一個參數;

主構造函數的全部參數必須標註爲 val 或者 var

數據類不能是 abstractopensealed,或者 inner

數據類不能繼承其它的類(但能夠實現接口)。

在 JVM 中若是構造函數是無參的,則全部的屬性必須有默認的值,(參看Constructors);

data class User(val name: String = "", val age: Int = 0)

複製

咱們常常會對一些屬性作修改但想要其餘部分不變。這就是 copy() 函數的由來。在上面的 User 類中,實現起來應該是這樣:

fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
複製代碼

有了 copy 咱們就能夠像下面這樣寫了:

val jack = User(name = "jack", age = 1)
val olderJack = jack.copy(age = 2)
複製代碼

數據類和多重聲明

組件函數容許數據類在多重聲明中使用:

val jane = User("jane", 35)
val (name, age) = jane
println("$name, $age years of age") //打印出 "Jane, 35 years of age"
複製代碼

標準數據類

標準庫提供了 PairTriple。在大多數情形中,命名數據類是更好的設計選擇,由於這樣代碼可讀性更強並且提供了有意義的名字和屬性。

泛型

像 java 同樣,Kotlin 中能夠擁有類型參數:

class Box<T>(t: T){
    var value = t
}
複製代碼

一般來講,建立一個這樣類的實例,咱們須要提供類型參數:

val box: Box<Int> = Box<Int>(1)
複製代碼

但若是類型有多是推斷的,好比來自構造函數的參數或者經過其它的一些方式,一個能夠忽略類型的參數:

val box = Box(1)//1是 Int 型,所以編譯器會推導出咱們調用的是 Box<Int>
複製代碼

變化

java 類型系統最棘手的一部分就是通配符類型。但 kotlin 沒有,代替它的是兩種其它的東西:聲明變化和類型投影(declaration-site variance and type projections)。

首先,咱們想一想爲何 java 須要這些神祕的通配符。這個問題在Effective Java,條目18中是這樣解釋的:使用界限通配符增長 API 的靈活性。首先 java 中的泛型是不變的,這就意味着 List<String> 不是 List<Object> 的子類型。爲何呢,若是 List 不是不變的,就會引起下面的問題:

// Java
List<String> strs = new ArrayList<String>();
List<Object> objs = strs; // !!! The cause of the upcoming problem sits here. Java prohibits this!
objs.add(1); // Here we put an Integer into a list of Strings
String s = strs.get(0); // !!! ClassCastException: Cannot cast Integer to String
複製代碼

所以 java 禁止了這樣的事情來保證運行時安全。但這有些其它影響。好比,Collection 接口的 addAll() 方法。這個方法的簽名在哪呢?直觀來說是這樣的:

//java
interface Collection<E> ... {
    void addAdd(Collection<E> items);
}
複製代碼

但接下來咱們就不能作下面這些簡單事情了:

//java
void copyAll(Collection<Object> to, Collection<String> from){
    to.addAll(from);
}
複製代碼

這就是爲何 addAll() 的簽名是下面這樣的:

//java
interface Collection<E> ... {
    void addAll(Colletion<? extend E> items);
}
複製代碼

這個通配符參數 ? extends T 意味着這個方法接受一些 T 類型的子類而非 T 類型自己。這就是說咱們能夠安全的讀 T's(這裏表示 T 子類元素的集合),但不能寫,由於咱們不知道 T 的子類到底是什麼樣的,針對這樣的限制,咱們很想要這樣的行爲:Collection<String>Collection<? extens Object>的子類。In 「clever words」, the wildcard with an extends-bound (upper bound) makes the type covariant.

The key to understanding why this trick works is rather simple: if you can only take items from a collection, then using a collection of Strings and reading Objects from it is fine. Conversely, if you can only put items into the collection, it’s OK to take a collection of Objects and put Strings into it: in Java we have List<? super String> a supertype of List.

嵌套類

類能夠嵌套在其餘類中

class Outer {
    private val bar: Int = 1
    class Nested {
    	fun foo() = 2
    }
}

val demo = Outer.Nested().foo() //==2
複製代碼

內部類

類能夠標記爲 inner 這樣就能夠訪問外部類的成員。內部類擁有外部類的一個對象引用:

class Outer {
    private val bar: Int = 1
    inner class Inner {
    	fun foo() = bar
    }
}

val demo = Outer().Inner().foo() //==1
複製代碼

參看這裏瞭解更多 this 在內部類的用法

匿名內部類

匿名內部類的實例是經過 對象表達式 建立的:

window.addMouseListener(object: MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
        // ...
    }
                                                                                                            
    override fun mouseEntered(e: MouseEvent) {
        // ...
    }
})
複製代碼

若是對象是函數式的 java 接口的實例(好比只有一個抽象方法的 java 接口),你能夠用一個帶接口類型的 lambda 表達式建立它。

val listener = ActionListener { println("clicked") }
複製代碼

枚舉類

枚舉類最基本的用法就是實現類型安全的枚舉

enum class Direction {
    NORTH,SOUTH,WEST
}
複製代碼

每一個自舉常量都是一個對象。枚舉常量經過逗號分開。

初始化

由於每一個枚舉都是枚舉類的一個實例,它們是能夠初始化的。

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}
複製代碼

匿名類

枚舉實例也能夠聲明它們本身的匿名類

enum class ProtocolState {
    WAITING {
    	override fun signal() = Taking
    },
    Taking{
    	override fun signal() = WAITING
    };
    abstract fun signal(): ProtocolState
}
複製代碼

能夠有對應的方法,以及複寫基本方法。注意若是枚舉定義了任何成員,你須要像在 java 中那樣用分號 ; 把枚舉常量定義和成員定義分開。

使用枚舉常量

像 java 同樣,Kotlin 中的枚舉類有合成方法容許列出枚舉常量的定義而且經過名字得到枚舉常量。這些方法的簽名就在下面列了出來(假設枚舉類名字是 EnumClass):

EnumClass.valueOf(value: String): EnumClass
EnumClass.values(): Array<EnumClass>
複製代碼

若是指定的名字在枚舉類中沒有任何匹配,那麼valueOf()方法將會拋出參數異常。

每一個枚舉常量都有獲取在枚舉類中聲明的名字和位置的方法:

name(): Sting
ordinal(): Int
複製代碼

枚舉類也實現了 Comparable 接口,比較時使用的是它們在枚舉類定義的天然順序。

對象表達式和聲明

有時候咱們想要建立一個對當前類有一點小修改的對象,但不想從新聲明一個子類。java 用匿名內部類的概念解決這個問題。Kotlin 用對象表達式和對象聲明巧妙的實現了這一律念。

對象表達式

經過下面的方式能夠建立繼承自某種(或某些)匿名類的對象:

window.addMouseListener(object: MouseAdapter () {
    override fun mouseClicked(e: MouseEvent) {
    	//...
    }
})
複製代碼

若是父類有構造函數,則必須傳遞相應的構造參數。多個父類能夠用逗號隔開,跟在冒號後面:

open class A(x: Int) {
    public open val y: Int = x
}

interface B { ... }

val ab = object : A(1), B {
    override val y = 14
}
複製代碼

有時候咱們只是須要一個沒有父類的對象,咱們能夠這樣寫:

val adHoc = object {
    var x: Int = 0
    var y: Int = 0
}

print(adHoc.x + adHoc.y)
複製代碼

像 java 的匿名內部類同樣,對象表達式能夠訪問閉合範圍內的變量 (和 java 不同的是,這些變量不用是 final 修飾的)

fun countClicks(windows: JComponent) {
    var clickCount = 0
    var enterCount = 0
    window.addMouseListener(object : MouseAdapter() {
    	override fun mouseClicked(e: MouseEvent) {
            clickCount++
    	}
    	override fun mouseEntered(e: MouseEvent){
            enterCount++
    	}
    })
}
複製代碼

對象聲明

單例模式是一種頗有用的模式,Kotln 中聲明它很方便:

object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        // ...
    }

    val allDataProviders: Collection<DataProvider>
        get() = // ...
}
複製代碼

這叫作對象聲明,跟在 object 關鍵字後面是對象名。和變量聲明同樣,對象聲明並非表達式,並且不能做爲右值用在賦值語句。

想要訪問這個類,直接經過名字來使用這個類:

DataProviderManager.registerDataProvider(...)
複製代碼

這樣類型的對象能夠有父類型:

object DefaultListener : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
        // ...
    }

    override fun mouseEntered(e: MouseEvent) {
        // ...
    }
}
複製代碼

注意:對象聲明不能夠是局部的(好比不能夠直接在函數內部聲明),但能夠在其它對象的聲明或非內部類中進行內嵌入

伴隨對象

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

class MyClass {
    companion object Factory {
    	fun create(): MyClass = MyClass()
    }
}
複製代碼

伴隨對象的成員能夠經過類名作限定詞直接使用:

val instance = MyClass.create()
複製代碼

在使用了 companion 關鍵字時,伴隨對象的名字能夠省略:

class MyClass {
    companion object {
    
    }
}
複製代碼

注意,儘管伴隨對象的成員很像其它語言中的靜態成員,但在運行時它們仍然是真正對象的成員實例,好比能夠實現接口:

inerface Factory<T> {
    fun create(): T
}

class MyClass {
    companion object : Factory<MyClass> {
    	override fun create(): MyClass = MyClass()
    }
}
複製代碼

若是你在 JVM 上使用 @JvmStatic 註解,你能夠有多個伴隨對象生成爲真實的靜態方法和屬性。參看 java interoperabillity。

對象表達式和聲明的區別

他倆之間只有一個特別重要的區別:

對象表達式在咱們使用的地方當即初始化並執行的

對象聲明是懶加載的,是在咱們第一次訪問時初始化的。

伴隨對象是在對應的類加載時初始化的,和 Java 的靜態初始是對應的。

代理

類代理

代理模式 給實現繼承提供了很好的代替方式, Kotlin 在語法上支持這一點,因此並不須要什麼樣板代碼。Derived 類能夠繼承 Base 接口而且指定一個對象代理它所有的公共方法:

interface Base {
    fun print()
}

class BaseImpl(val x: Int) : Base {
    override fun print() { printz(x) }
}

class Derived(b: Base) : Base by b

fun main() {
    val b = BaseImpl(10)
    Derived(b).print()
}
複製代碼

Derived 的父類列表中的 by 從句會將 b 存儲在 Derived 內部對象,而且編譯器會生成 Base 的全部方法並轉給 b

代理屬性

不少經常使用屬性,雖然咱們能夠在須要的時候手動實現它們,但更好的辦法是一次實現屢次使用,並放到庫。好比:

延遲屬性:只在第一次訪問是計算它的值 觀察屬性:監聽者從這獲取這個屬性更新的通知 在 map 中存儲的屬性,而不是單獨存在分開的字段

爲了知足這些情形,Kotllin 支持代理屬性:

class Example {
    var p: String by Delegate()
}
複製代碼

語法結構是: val/var <property name>: <Type> by <expression> 在 by 後面的屬性就是代理,這樣這個屬性的 get() 和 set() 方法就代理給了它。

屬性代理不須要任何接口的實現,但必需要提供 get() 方法(若是是變量還須要 set() 方法)。像這樣:

class Delegate {
    fun get(thisRef: Any?, prop: PropertyMetadata): String {
    	return "$thisRef, thank you for delegating '${prop.name}' to me !"
    }
    
    fun set(thisRef: Any?, prop: PropertyMatada, value: String) {
    	println("$value has been assigned to '${prop.name} in $thisRef.'")
    }
}
複製代碼

當咱們從 p 也就是 Delegate 的代理,中讀東西時,會調用 Delegateget() 函數,所以第一個參數是咱們從 p 中讀取的,第二個參數是 p 本身的一個描述。好比:

val e = Example()
pintln(e.p)
複製代碼

打印結果: 

Example@33a17727, thank you for delegating ‘p’ to me!

一樣當咱們分配 pset() 函數就會調動。前倆個參數因此同樣的,第三個持有分配的值:

e.p = "NEW"
複製代碼

打印結果: 

NEW has been assigned to ‘p’ in Example@33a17727.

代理屬性的要求

這裏總結一些代理對象的要求。

只讀屬性 (val),代理必須提供一個名字叫 get 的方法並接受以下參數:

接收者--必須是相同的,或者是屬性擁有者的子類型

元數據--必須是 PropertyMetadata 或這它的子類型

這個函數必須返回一樣的類型做爲屬性。

可變屬性 (var),代理必須添加一個叫 set 的函數並接受以下參數:

接受者--與 get() 同樣
元數據--與 get() 同樣
新值--必須和屬性類型一致或是它的字類型

標準代理

kotlin.properties.Delegates 對象是標準庫提供的一個工廠方法並提供了不少有用的代理

延遲

Delegate.lazy() 是一個接受 lamdba 並返回一個實現延遲屬性的代理:第一次調用 get() 執行 lamdba 並傳遞 lazy() 並記下結果,隨後調用 get() 並簡單返回以前記下的值。

import kotlin.properties.Delegates

val lazy: String by Delegates.lazy {
    println("computed!")
    "Hello"
}

fun main(args: Array<String>) {
    println(lazy)
    println(lazy)
}
複製代碼

若是你想要線程安全,使用 blockingLazy(): 它仍是按照一樣的方式工做,但保證了它的值只會在一個線程中計算,而且全部的線程都獲取的同一個值。

觀察者

Delegates.observable() 須要倆個參數:一個初始值和一個修改者的 handler 。每次咱們分配屬性時都會調用handler (在分配前執行)。它有三個參數:一個分配的屬性,舊值,新值:

class User {
    var name: String by Delegates.observable("<no name>") {
    	d.old,new -> println("$old -> $new")
    }
}

fun main(args: Array<String>) {
    val user = User()
    user.name = "first"
    user.name = "second"
}
複製代碼

打印結果

-> first first -> second

若是你想可以截取它的分配並取消它,用 vetoable()代替 observable()

非空

有時咱們有一個非空的 var ,但咱們在構造函數中沒有一個合適的值,好比它必須稍後再分配。問題是你不能持有一個未初始化而且是非抽象的屬性:

class Foo {
	var bar: Bat //錯誤必須初始化
}
複製代碼

咱們能夠用 null 初始化它,但咱們不用每次訪問時都檢查它。

Delegates.notNull()能夠解決這個問題

class Foo {
    var bar: Bar by Delegates.notNull()
}
複製代碼

若是這個屬性在第一次寫以前讀,它就會拋出一個異常,只有分配以後纔會正常。

在 Map 中存儲屬性

Delegates.mapVal() 擁有一個 map 實例並返回一個能夠從 map 中讀其中屬性的代理。在應用中有不少這樣的例子,好比解析 JSON 或者作其它的一些 "動態"的事情:

class User(val map: Map<String, Any?>) {
    val name: String by Delegates.mapVal(map)
    val age: Int     by Delegates.mapVal(map)
}
複製代碼

在這個例子中,構造函數持有一個 map :

val user = User(mapOf (
    "name" to "John Doe",
    "age" to 25
))
複製代碼

代理從這個 map 中取指(經過屬性的名字):

println(user.name) // Prints "John Doe"
println(user.age)  // Prints 25
複製代碼

var 能夠用 mapVar

函數

函數聲明

在 kotlin 中用關鍵字 fun 聲明函數:

fun double(x: Int): Int {

}
複製代碼

函數用法

經過傳統的方法調用函數

val result = double(2) 
複製代碼

經過.調用成員函數

Sample().foo() // 建立Sample類的實例,調用foo方法
複製代碼

中綴符號

在知足如下條件時,函數也能夠經過中綴符號進行調用:

它們是成員函數或者是擴展函數  只有一個參數 使用infix關鍵詞進行標記

//給 Int 定義一個擴展方法
infix fun Int.shl(x: Int): Int {
    ...
}

1 shl 2 //用中綴註解調用擴展函數

1.shl(2)
複製代碼

參數

函數參數是用 Pascal 符號定義的 name:type。參數之間用逗號隔開,每一個參數必須指明類型。

fun powerOf(number: Int, exponent: Int) {
    ...
}
複製代碼

默認參數

函數參數能夠設置默認值,當參數被忽略時會使用默認值。這樣相比其餘語言能夠減小重載。

fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size() ) {
    ...
}
複製代碼

默認值能夠經過在type類型後使用=號進行賦值

命名參數

在調用函數時參數能夠命名。這對於那種有大量參數的函數是很方便的.

下面是一個例子:

fun reformat(str: String, normalizeCase: Boolean = true,upperCaseFirstLetter: Boolean = true, divideByCamelHumps: Boolean = false, wordSeparator: Char = ' ') {
    ...
}
複製代碼

咱們可使用默認參數

reformat(str)

然而當調用非默認參數是就須要像下面這樣:

reformat(str, true, true, false, '_')
複製代碼

使用命名參數咱們可讓代碼可讀性更強:

reformat(str,
    normalizeCase = true,
    uppercaseFirstLetter = true,
    divideByCamelHumps = false,
    wordSeparator = '_'
  )
複製代碼

若是不須要所有參數的話能夠這樣:

reformat(str, wordSeparator = '_')
複製代碼

注意,命名參數語法不可以被用於調用Java函數中,由於Java的字節碼不能確保方法參數命名的不變性

不帶返回值的參數

若是函數不會返回任何有用值,那麼他的返回類型就是 Unit .Unit 是一個只有惟一值Unit的類型.這個值並不須要被直接返回:

fun printHello(name: String?): Unit {
    if (name != null)
        println("Hello ${name}")
    else
        println("Hi there!")
    // `return Unit` or `return` is optional
}
複製代碼

Unit 返回值也能夠省略,好比下面這樣:

fun printHello(name: String?) {
    ...
}
複製代碼

單表達式函數

當函數只返回單個表達式時,大括號能夠省略並在 = 後面定義函數體

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

在編譯器能夠推斷出返回值類型的時候,返回值的類型能夠省略:

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

明確返回類型

下面的例子中必須有明確返回類型,除非他是返回 Unit類型的值,Kotlin 並不會對函數體重的返回類型進行推斷,由於函數體中可能有複雜的控制流,他的返回類型未必對讀者可見(甚至對編譯器而言也有多是不可見的):

變長參數

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

fun asList<T>(vararg ts: T): List<T> {
    val result = ArrayList<T>()
    for (t in ts)
    	result.add(t)
    return result
}
複製代碼

標記後,容許給函數傳遞可變長度的參數:

val list = asList(1, 2, 3)
複製代碼

只有一個參數能夠被標註爲 vararg 。加入vararg並非列表中的最後一個參數,那麼後面的參數須要經過命名參數語法進行傳值,再或者若是這個參數是函數類型,就須要經過lambda法則.

當調用變長參數的函數時,咱們能夠一個一個的傳遞參數,好比 asList(1, 2, 3),或者咱們要傳遞一個 array 的內容給函數,咱們就可使用 * 前綴操做符:

val a = array(1, 2, 3)
val list = asList(-1, 0, *a, 4)
複製代碼

函數範圍

Kotlin 中能夠在文件頂級聲明函數,這就意味者你不用像在Java,C#或是Scala同樣建立一個類來持有函數。除了頂級函數,Kotlin 函數能夠聲明爲局部的,做爲成員函數或擴展函數。

局部函數

Kotlin 支持局部函數,好比在一個函數包含另外一函數。

fun dfs(graph: Graph) {
    fun dfs(current: Vertex, visited: Set<Vertex>) {
    if (!visited.add(current)) return
    for (v in current.neighbors)
        dfs(v, visited)
    }
    
    dfs(graph.vertices[0], HashSet())
}
複製代碼

局部函數能夠訪問外部函數的局部變量(好比閉包)

fun dfs(graph: Graph) {
    val visited = HashSet<Vertex>()
    fun dfs(current: Vertex) {
    	if (!visited.add(current)) return 
    	for (v in current.neighbors)
            dfs(v)
    }
    dfs(graph.vertices[0])
}
複製代碼

局部函數甚至能夠返回到外部函數 qualified return expressions

fun reachable(from: Vertex, to: Vertex): Boolean {
	val visited = HashSet<Vertex>()
	fun dfs(current: Vertex) {
		if (current == to) return@reachable true
		if (!visited.add(current)) return
		for (v  in current.neighbors)
			dfs(v)
	}
	dfs(from)
	return false
}
複製代碼

成員函數

成員函數是定義在一個類或對象裏邊的

class Sample() {
    fun foo() { print("Foo") }
}
複製代碼

成員函數能夠用 . 的方式調用

Sample.foo()
複製代碼

更多請參看類和繼承

泛型函數

函數能夠有泛型參數,樣式是在函數後跟上尖括號。

fun sigletonArray<T>(item: T): Array<T> {
    return Array<T>(1, {item})
}
複製代碼

更多請參看泛型

內聯函數

參看這裏

擴展函數

參看這裏

高階函數和 lambda 表達式

參看這裏

尾遞歸函數

Kotlin 支持函數式編程的尾遞歸。這個容許一些算法能夠經過循環而不是遞歸解決問題,從而避免了棧溢出。當函數被標記爲 tailrec 時,編譯器會優化遞歸,並用高效迅速的循環代替它。

tailrec fun findFixPoint(x: Double = 1.0): Double 
	= if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))
複製代碼

這段代碼計算的是數學上的餘弦不動點。Math.cos 從 1.0 開始不斷重複,直到值不變爲止,結果是 0.7390851332151607 這段代碼和下面的是等效的:

private fun findFixPoint(): Double {
    var x = 1.0
    while (true) {
    	val y = Math.cos(x)
    	if ( x == y ) return y
    	x = y
    }
}
複製代碼

使用 tailrec 修飾符必須在最後一個操做中調用本身。在遞歸調用代碼後面是不容許有其它代碼的,而且也不能夠在 try/catch/finall 塊中進行使用。當前的尾遞歸只在 JVM 的後端中能夠用

高階函數與 lambda 表達式

高階函數

高階函數就是能夠接受函數做爲參數並返回一個函數的函數。好比 lock() 就是一個很好的例子,它接收一個 lock 對象和一個函數,運行函數並釋放 lock;

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

如今解釋一下上面的代碼吧:body 有一個函數類型 () -> T,把它設想爲沒有參數並返回 T 類型的函數。它引起了內部的 try 函數塊,並被 lock 保護,結果是經過 lock() 函數返回的。

若是咱們想調用 lock() ,函數,咱們能夠傳給它另外一個函數作參數,參看函數參考

fun toBeSynchroized() = sharedResource.operation()

val result = lock(lock, ::toBeSynchroized)
複製代碼

其實最方便的辦法是傳遞一個字面函數(一般是 lambda 表達式):

val result = lock(lock, {
sharedResource.operation() })
複製代碼

字面函數常常描述有更多細節,但爲了繼續本節,咱們看一下更簡單的預覽吧:

字面函數被包在大括號裏

參數在 -> 前面聲明(參數類型能夠省略)

函數體在 -> 以後

在 kotlin 中有一個約定,若是最後一個參數是函數,能夠省略括號:

lock (lock) {
    sharedResource.operation()
}
複製代碼

最後一個高階函數的例子是 map() (of MapReduce):

fun <T, R> List<T>.map(transform: (T) -> R):
List<R> {
    val result = arrayListOf<R>()
    for (item in this)
    	result.add(transform(item))
    return result
}
複製代碼

函數能夠經過下面的方式調用

val doubled = ints.map {it -> it * 2}
複製代碼

若是字面函數只有一個參數,則聲明能夠省略,名字就是 it :

ints map {it * 2}
複製代碼

這樣就能夠寫LINQ-風格的代碼了:

strings filter {it.length == 5} sortBy {it} map {it.toUpperCase()}
複製代碼

內聯函數

有些時候能夠用 內聯函數 提升高階函數的性能。

字面函數和函數表達式

字面函數或函數表達式就是一個 "匿名函數",也就是沒有聲明的函數,但當即做爲表達式傳遞下去。想一想下面的例子:

max(strings, {a, b -> a.length < b.length })
複製代碼

max 函數就是一個高階函數,它接受函數做爲第二個參數。第二個參數是一個表達式因此本生就是一個函數,即字面函數。做爲一個函數,至關於:

fun compare(a: String, b: String) : Boolean = a.length < b.length
複製代碼

函數類型

一個函數要接受另外一個函數做爲參數,咱們得給它指定一個類型。好比上面的 max 定義是這樣的:

fun max<T>(collection: Collection<out T>, less: (T, T) -> Boolean): T? {
    var max: T? = null
    for (it in collection)
    	if (max == null || less(max!!, it))
            max = it
    return max
}
複製代碼

參數 less(T, T) -> Boolean類型,也就是接受倆個 T 類型參數返回一個 Boolean:若是第一個參數小於第二個則返回真。

在函數體第四行, less 是用做函數

一個函數類型能夠像上面那樣寫,也可有命名參數,更多參看命名參數

val compare: (x: T,y: T) -> Int = ...
複製代碼

函數文本語法

函數文本的徹底寫法是下面這樣的:

val sum = {x: Int,y: Int -> x + y}
複製代碼

函數文本老是在大括號裏包裹着,在徹底語法中參數聲明是在括號內,類型註解是可選的,函數體是在 -> 以後,像下面這樣:

val sum: (Int, Int) -> Int = {x, y -> x+y }
複製代碼

函數文本有時只有一個參數。若是 kotlin 能夠從它本生計算出簽名,那麼能夠省略這個惟一的參數,並會經過 it 隱式的聲明它:

ints.filter {it > 0}//這是 (it: Int) -> Boolean 的字面意思 注意若是一個函數接受另外一個函數作爲最後一個參數,該函數文本參數能夠在括號內的參數列表外的傳遞。參看 callSuffix

函數表達式

上面沒有講到能夠指定返回值的函數。在大多數情形中,這是沒必要要的,由於返回值是能夠自動推斷的。然而,若是你須要本身指定,能夠用函數表達式來作:

fun(x: Int, y: Int ): Int = x + y
複製代碼

函數表達式很像普通的函數聲明,除了省略了函數名。它的函數體能夠是一個表達式(像上面那樣)或者是一個塊:

fun(x: Int, y: Int): Int {
    return x + y
}
複製代碼

參數以及返回值和普通函數是同樣的,若是它們能夠從上下文推斷出參數類型,則參數能夠省略:

ints.filter(fun(item) = item > 0)
複製代碼

返回值類型的推導和普通函數同樣:函數返回值是經過表達式自動推斷並被明確聲明

注意函數表達式的參數老是在括號裏傳遞的。 The shorthand syntax allowing to leave the function outside the parentheses works only for function literals.

字面函數和表達式函數的另外一個區別是沒有本地返回。沒有 lable 的返回老是返回到 fun 關鍵字所聲明的地方。這意味着字面函數內的返回會返回到一個閉合函數,而表達式函數會返回到函數表達式自身。

閉包

一個字面函數或者表達式函數能夠訪問閉包,即訪問自身範圍外的聲明的變量。不像 java 那樣在閉包中的變量能夠被捕獲修改:

var sum = 0

ins filter {it > 0} forEach {
    sum += it
}
print(sum)
複製代碼

函數表達式擴展

除了普通的功能,kotlin 支持擴展函數。這種方式對於字面函數和表達式函數都是適用的。它們最重要的使用是在 Type-safe Groovy-style builders

表達式函數的擴展和普通的區別是它有接收類型的規範。

val sum = fun Int.(other: Int): Int = this + other
複製代碼

接收類型必須在表達式函數中明確指定,但字面函數不用。字面函數能夠做爲擴展函數表達式,但只有接收類型能夠經過上下文推斷出來。

表達式函數的擴展類型是一個帶接收者的函數:

sum : Int.(other: Int) -> Int
複製代碼

能夠用 . 或前綴來使用這樣的函數:

1.sum(2)
1 sum 2
複製代碼

內聯函數

使用高階函數帶來了相應的運行時麻煩:每一個函數都是一個對象,它捕獲閉包,即這些變量能夠在函數體內被訪問。內存的分配,虛擬調用的運行都會帶來開銷

但在大多數這種開銷是能夠經過內聯文本函數避免。下面就是一個很好的例子。lock() 函數能夠很容易的在內聯點調用。思考一下下面的例子:

lock(i) { foo() }
複製代碼

(Instead of creating a function object for the parameter and generating a call),編譯器能夠忽略下面的代碼:

lock.lock()
try {
    foo()
}
finally {
    lock.lock()
}
複製代碼

這好像不是咱們開始想要的

想要讓編譯器不這樣作的話,咱們須要用 inline 標記 lock() 函數:

inline fun lock<T>(lock: Lock,body: ()-> T): T {
	//...
}
複製代碼

inline 標記即影響函數自己也影響傳遞進來的 lambda 函數:全部的這些都將被關聯到調用點。

內聯可能會引發生成代碼增加,但咱們能夠合理的解決它(不要內聯太大的函數)

@noinline

爲了你想要一些 lambda 表達式傳遞給內聯函數時是內聯的,你能夠給你的一些函數參數標記 @noinline 註解:

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

內聯的 lambda 只能在內聯函數中調用,或者做爲內聯參數,但 @noinline 標記的能夠經過任何咱們喜歡的方式操控:存儲在字段,( passed around etc)

注意若是內聯函數沒有內聯的函數參數而且沒有具體類型的參數,編譯器會報警告,這樣內聯函數就沒有什麼優勢的(若是你認爲內聯是必須的你能夠忽略警告)

返回到非局部

在 kotlin 中,咱們能夠不加條件的使用 return 去退出一個命名函數或表達式函數。這意味這退出一個 lambda 函數,咱們不得不使用標籤,並且空白的 return 在 lambda 函數中是禁止的,由於 lambda 函數不能夠造一個閉合函數返回:

fun foo() {
    ordinaryFunction {
    	return // 錯誤&emsp;不能夠在這返回
    }
}
複製代碼

但若是 lambda 函數是內聯傳遞的,則返回也是能夠內聯的,所以容許下面這樣:

fun foo() {
    inlineFunction {
    	return //
    ]
}
複製代碼

注意有些內聯函數能夠調用傳遞進來的 lambda 函數,但不是在函數體,而是在另外一個執行的上下文中,好比局部對象或者一個嵌套函數。在這樣的情形中,非局部的控制流也不容許在lambda 函數中。爲了代表,lambda 參數須要有 InlineOptions.ONLY_LOCAL_RETURN 註解:

inline fun f(inlineOptions(InlineOption.ONLY_LOCAL_RETURN) body: () -> Unit) {
    val f = object: Runnable {
        override fun run() = body()
    }
    // ...
}
複製代碼

內聯 lambda 不容許用 break 或 continue ,但在之後的版本可能會支持。

實例化參數類型

有時候咱們須要訪問傳遞過來的類型做爲參數:

fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
    var p = parent
    while (p != null && !clazz.isInstance(p)) {
    	p = p?.parent
    }
    @suppress("UNCHECKED_CAST")
    return p as T
}
複製代碼

如今,咱們創立了一顆樹,並用反射檢查它是不是某個特定類型。一切看起來很好,但調用點就很繁瑣了:

myTree.findParentOfType(javaClass<MyTreeNodeType>() )
複製代碼

咱們想要的僅僅是給這個函數傳遞一個類型,即像下面這樣:

myTree.findParentOfType<MyTreeNodeType>()
複製代碼

爲了達到這個目的,內聯函數支持具體化的類型參數,所以咱們能夠寫成這樣:

inline fun <reified T> TreeNode.findParentOfType(): T? {
    var p = parent
    while (p != null && p !is T) {
    	p = p?.parent
    }
    return p as T
}
複製代碼

咱們用 refied 修飾符檢查類型參數,既然它能夠在函數內部訪問了,也就基本上接近普通函數了。由於函數是內聯的,因此不準要反射,像 !is as這樣的操做均可以使用。同時,咱們也能夠像上面那樣調用它了 myTree.findParentOfType<MyTreeNodeType>()

儘管在不少狀況下會使用反射,咱們仍然可使用實例化的類型參數 javaClass() 來訪問它:

inline fun methodsOf<reified T>() = javaClass<T>().getMethods()

fun main(s: Array<String>) {
    println(methodsOf<String>().joinToString('\n'))
}
複製代碼

普通的函數(沒有標記爲內聯的)不能有實例化參數。

更底層的解釋請看spec document


非空類型

在Kotlin中定義一個變量時,默認就是非空類型。當你將一個非空類型置空時,編譯器會告訴你這不可行。

所以,若是你後面任什麼時候候使用該變量時,均可以放心的使用而不用擔憂會發生NPE。因此要想遠離NPE,儘量的使用非空類型的定義

例如:

var a: String = "abc"
a = null // compilation error
複製代碼

可選型(可空類型)

雖然非空類型可以有效避免 NPE 的問題,可是有時候咱們總不可避免的須要使用可選類型。在定義可選型的時候,咱們只要在非空類型的後面添加一個 ? 就能夠了。

var b: String? = "abc"
b = null // ok
複製代碼

在使用可選型變量的時候,這個變量就有可能爲空,因此在使用前咱們應該對其進行空判斷(在 Java 中咱們常常這樣作),這樣每每帶來帶來大量的工做,這些空判斷代碼自己沒有什麼實際意義,而且讓代碼的可讀性和簡潔性帶來了巨大的挑戰。

Kotlin 爲了解決這個問題,它並不容許咱們直接使用一個可選型的變量去調用方法或者屬性。

var b: String? = null
val l = b.length // compilation error
複製代碼

若是使用這種方法,那麼空判斷是必須的。

val l = if (b != null) b.length else -1
複製代碼

注意:若是你定義的變量是全局變量,即便你作了空判斷,依然不能使用變量去調用方法或者屬性。 這個時候你須要考慮使用下面的方法。

Kotlin 爲可選型提供了一個安全調用操做符 ?.,使用該操做符能夠方便調用可選型的方法或者屬性。

val l = b?.length
複製代碼

這裏 l 獲得的返回依然是一個可選型 Int?

Kotlin 還提供了一個強轉的操做符!!,這個操做符可以強行調用變量的方法或者屬性,而無論這個變量是否爲空,若是這個時候該變量爲空時,那麼就會發生 NPE。因此若是不想繼續陷入 NPE 的困境沒法自拔,請不要該操做符走的太近。

Elvis 操做符

上面有提到一種狀況,當 b 爲空時,返回它的長度值給一個默認值 -1。要實現這樣的邏輯固然能夠用 if else 的邏輯判斷實現,但 Kotlin 提供了一個更優雅的書寫方式 ?:

val l = b?.length ?: -1
複製代碼

b?.length ?: -1if (b != null) b.length else -1 徹底等價的。

其實你還能夠在 ?: 後面添加任何表達式,好比你能夠在後面會用 returnthrow(在 Kotlin 中它們都是表達式)。

fun foo(node: Node): String? {
  val parent = node.getParent() ?: return null
  val name = node.getName() ?: throw IllegalArgumentException("name expected")
  // ...
}
複製代碼

let 函數

let 是官方 stdlib 提供的標準函數庫裏面的函數,這個函數巧妙的利用的Kotlin語言的特性讓let接受的表達式參數中的調用方是非空的。

val listWithNulls: List<String?> = listOf("A", null)
for (item in listWithNulls) {
    item?.let { println(it) } // prints A and ignores null
}
複製代碼

上面代碼的只會輸出 A,而不會輸出 null

須要注意的是,這個方法調用的時候必需要使用?.操做符調用才能生效哦。若是你的部分代碼依賴於一個可選型變量爲非空的時候,就可使用 let 函數。



未完待續~






About Me

相關文章
相關標籤/搜索