Kotlin in Action 筆記

Kotlin

參考

官網 referencejava

kotlin實戰mysql

Try Kotlingit

Kotlin Chinagithub

Githubweb

簡介

Kotlin是一門把Java平臺做爲目標的新的編程語言。它簡潔、安全、優雅並且專一於和Java代碼間的互操做性。它幾乎能夠用於現在Java遍佈的全部地方.spring

  • Kotlin是靜態類型的,支持類型推斷的,在保持代碼精簡的同時維持準確性和性能。
  • kotlin同時支持面向對象和函數式編程風格,經過把函數放在一等公民的位置實現更高層次的抽象,經過支持不可變值簡化了測試和多線程開發。
  • Kotlin在服務器端應用運行良好。它能全面支持現有的Java框架併爲公共任務提供了新的工具,例如生成HTML和保持一致性。
  • Kotlin是免費和開源的。它爲主流IDE和構建系統提供了全面的支持。
  • Kotlin是優雅的、安全的、精簡的以及互操做性強的(語言)。這意味着它專一於使用已經被證實的方案來解決常見任務,阻止通常的錯誤,例如:NullPointerException,支持緊湊和易讀的代碼,鬆散的Java集成功能。

kotlin構建流程:
sql

函數和變量

Hello,world!

fun main(args: Array<String>) {
    println("Hello, world!")
}
  • 用fun來定義一個函數;
  • 函數能夠不依賴類而存在;
  • 參數類型寫在參數名後面;
  • 數組只是一個類, 沒有聲明數組的特殊語法;
  • 能夠省略行末的分號.

函數

函數聲明以fun關鍵字爲開始,接着是函數名:max。接着是圓括號中的參數列表。返回類型跟在參數列表後面,以冒號分隔。數據庫

在kotlin中if是一個表達式; if的分支能夠是代碼塊, 代碼塊中最後的表達式就是這個代碼塊的值; 若是if被用做表達式(好比返回值或者給變量賦值), 則須要有else分支.express

若是函數的內容部分只有一個表達式,你能夠移除大括號和return聲明,使用表達式做爲整個函數的主體;對於表達式主體函數來講,編譯器可以分析用作主函數主體的表達式,並使用表達式的類型做爲函數返回類型.只有表達式函數才容許忽略返回值。編程

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

命名參數和默認參數

定義參數時,默認參數值能夠減小函數的重載:

fun <T> joinToString(
        collection: Collection<T>,
        separator: String = ", ",
        prefix: String = "",
        postfix: String = ""
): String

調用Kotlin中定義的函數時, 能夠指定參數的名稱, 使代碼更易讀, 但調用Java中的方法並不行:

// 有默認參數值的參數可省略
joinToString(collection = collection, postfix = " ")

變量

有兩個關鍵詞來聲明一個變量:

  • val 不可變的引用, 對應於final變量。
  • var 可變的引用, 對應於非final變量。

若是變量有初始化器, 能夠忽略變量的類型聲明:

val message = "Success"
final String message = "Success";

一個val變量必須在定義塊執行時被初始化並且只能一次。若是編譯器可以確保惟一的初始化聲明可以其中一個被執行, 你能夠根據狀況用不一樣的值初始化變量:

val message: String
if ( canPerformOperation()) {
    message = "Success"
} else {
    message = "Failed"
}

字符串模版

字符串能夠包含模板表達式, 即一些小段代碼, 會求值並把結果合併到字符串中.

val s = "abc"
val str = "$s.length is ${s.length}" // "abc.length is 3"

val price = """
${'$'}9.99
"""

類和屬性

聲明一個有name屬性的類:

class Person(val name: String)

在Java中:

public class Person{
    private String name;
    
    public Person(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
}

在Kotlin中的一個類能夠有一個主構造函數和一個或多個次構造函數.主構造函數是類頭的一部分: 它跟在類名(和可選的類型參數)後

class Person(firstName: String) {
}

若是主構造函數沒有任何註解或者可見性修飾符, 能夠省略這個 constructor關鍵字.

class Person(firstName: String) {
}

主構造函數不能包含任何的代碼. 初始化的代碼能夠放到以 init 關鍵字做爲前綴的初始化塊中:

class Customer(name: String) {
    val customerKey = name.toUpperCase()
    init {
        logger.info("Customer initialized with value ${name}")
    }
}
class Person(val firstName: String, val lastName: String, var age: Int) {
    // ……
}

聲明次構造函數

class Person {
    constructor(parent: Person) {
        parent.children.add(this)
    }
}

若是類有一個主構造函數, 每一個次構造函數須要委託給主構造函數, 能夠直接委託或者經過別的次構造函數間接委託:

class Person(val name: String) {
    constructor(name: String, parent: Person) : this(name) {
        parent.children.add(this)
    }
}

屬性

聲明屬性的完整語法:

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

kotlin中,類沒有字段。

class Person(
    val name: String, // 只讀屬性,生成一個幕後字段和一個簡單的getter
    var isMarried: Boolean // 可寫屬性,生成一個幕後字段和簡單的getter和setter
)

若是屬性的名稱以"is"開頭,那麼它的getter不會增長任何前綴,而且它的setter會把is替換成set。

// java
Person person = new Person("fjh", false);
System.out.println(person.getName());
System.out.println(person.isMarried());
// kotlin
val person = Person("fjh", false)
println(person.name)
println(person.isMarried)

自定義訪問器:

class Rectangle(val height: Int, val width: Int) {
    val isSquare: Boolean
        get() {
            return height == width
        }
}
class Rectangle(val height: Int, val width: Int) {
    fun isSquare(): Boolean = height == width
}

繼承

Kotlin 須要顯式標註可覆蓋的成員和覆蓋後的成員, 除了用abstract標記的抽象類和抽象成員.

在Kotlin中全部類都有一個共同的超類Any, 這對於沒有超類型聲明的類是默認超類; Any 不是 java.lang.Object, 它除了equals(),hashCode()和toString() 外沒
有任何成員.

若是該類有一個主構造函數,其基類型能夠用主構造函數參數就
地初始化.

open class Base {
    open fun v() {}
    fun nv() {}
}
class Derived() : Base() {
    final override fun v() {} // 經過final禁止再次繼承
}

若是類沒有主構造函數, 那麼每一個次構造函數必須使用super關鍵字初始化其基類型, 或委託給另外一個構造函數作到這一點.

class MyView : View {
    constructor(ctx: Context) : super(ctx)
    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}

接口

Kotlin 的接口與 Java 8 相似,既包含抽象方法的聲明,也包含實現。與抽象類不一樣的是,接口沒法保存狀態。它能夠有屬性但必須聲明爲抽象或提供訪問器實現。

interface MyInterface {
fun bar()
    fun foo() {
        // 可選的方法體
    }
}

帶有屬性的接口(若是接口定義了getter那麼它的的getter不能引用屬性):

interface MyInterface {
    val prop: Int // 抽象的
    val propertyWithImplementation: String
    get() = "foo"
    fun foo() {
        print(prop)
    }
}

class Child : MyInterface {
    override val prop: Int = 29
}

內部類和嵌套類

像在Java中同樣, 類能夠嵌套在其餘類中. 但在Kotlin中, 嵌套類默認和Java中的靜態的內部類相似.

class Outer {
    private val bar: Int = 1
    class Nested {
        fun foo() = 2
    }
}
類A在另外一個類B中的聲明 Java Kotlin
嵌套類 static class A class A
內部類 class A inner class A

密封類

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

  • 密封類用sealed聲明;
  • 一個密封類是自身抽象的,它不能直接實例化並能夠有抽象成員;密封類不容許有非- private構造函數(其構造函數默認爲 private );
  • 擴展密封類子類的類(間接繼承者)能夠放在任何位置, 而無需在同一個文件中;
  • 密封類的全部子類都必須在與密封類自身相同的文件中聲明(在1.1以前, 必須在密封類內部).
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()

數據類

咱們常常建立一些只保存數據的類。在這些類中,一些標準函數每每是從數據機械推導而來的。在 Kotlin 中,這叫作 數據類 並標記爲 data :

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

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

  • equals() / hashCode() 對;
  • toString() 格式是 "User(name=John, age=42)" ;
  • componentN() 函數按聲明順序全部屬性(用於解構聲明);
  • copy() 函數。

在不少狀況下, 咱們須要複製一個對象改變它的一些屬性, 但其他部分保持不變. copy()函數就是爲此而生成.

fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
val fjh = User(name = "fjh", age = 22)
val olderFjh = fjh.copy(age = 23)

解構聲明

一個解構聲明同時建立多個變量:

class Person(val name: String, var age: Int) {
    operator fun component1() = name
    operator fun component2() = age
}

fun main(args: Array<String>) {
    val (name, age) = Person("fjh", 22)
    println(name)
    println(age)
}

會被編譯成一下代碼:

val name = person.component1()
val age = person.component2()

componentN() 函數須要用 operator 關鍵字標記,以容許在解構聲明中使用它們.

委託

類委託

類能夠繼承一個接口,並將其全部共有的方法委託給一個指定的對象:

interface Base {
    fun print()
}
class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}
class Derived(b: Base) : Base by b
fun main(args: Array<String>) {
    val b = BaseImpl(10)
    Derived(b).print() // 輸出 10
}

能夠覆蓋你想要修改的方法.

屬性委託

有一些常見的屬性類型,雖然咱們能夠在每次須要的時候手動實現它們, 可是若是可以把他們只實現一次並放入一個庫會更好。例如包括:

  • 延遲屬性(lazy properties): 其值只在首次訪問時計算;
  • 可觀察屬性(observable properties):監聽器會收到有關此屬性變動的通知;
  • 把多個屬性儲存在一個映射(map)中,而不是每一個存在單獨的字段中。
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 Example {
    var p: String by Delegate()
}

object關鍵字

object關鍵字不一樣於class, 它會聲明一個類並建立一個實例.

使用場景:

  • 對象聲明, 實現單例;
  • 伴生對象, 實現相似Java中的靜態成員;
  • 對象表達式, 代替Java匿名內部類.

伴生對象

Kotlin中的類不能有靜態成員, 而用包級別函數和對象聲明替代.

class MyClass {
    // 能夠省略伴生對象的名稱
    companion object Factory {
        fun create(): MyClass = MyClass()
    }
}

val instance = MyClass.create()

即便伴生對象的成員看起來像其餘語言的靜態成員,在運行時他們仍然是真實對象的實例成員.

val instance = MyClass.Factory.create()

對象表達式

fun countClicks(window: Window) {
    var clickCount = 0
    
    window.addMouseListener(object : MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
            clickCount++
        }
    })
}

擴展

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

擴展函數

fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // 「this」對應該列表
    this[index1] = this[index2]
    this[index2] = 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

擴展屬性

擴展屬性不能有初始化器

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

伴生對象擴展

class MyClass {
    companion object { } // 將被稱爲 "Companion"
}
fun MyClass.Companion.foo() {
    // ……
}

目錄和包

kotlin中目錄和包不須要對應;沒有import static。

源文件一般以包聲明開頭, 若是沒有指明包, 該文件的內容屬於無名字的默認包:

package utry
// ......

有多個包會默認導入到每一個Kotlin文件中:

  • 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.

若是出現名字衝突, 可使用 as 關鍵字在本地重命名衝突項來消歧義:

import foo.Bar // Bar 可訪問
import bar.Bar as bBar // bBar 表明「bar.Bar」

import 也能夠用來導入:

  • 頂層函數及屬性;
  • 在對象聲明中聲明的函數和屬性;
  • 枚舉常量.

可見性修飾符

在 Kotlin 中有這四個可見性修飾符: private 、protected 、 internal 和 public。若是沒有顯式指定修飾符的話,默承認見性是public 。

Kotlin中沒有Java的包私有, 而提供了internal(模塊內部可見).

可見性修飾符 internal意味着該成員只在相同模塊內可見。更具體地說,一個模塊是編譯在一塊兒的一套 Kotlin 文件:

  • 一個 IntelliJ IDEA 模塊;
  • 一個 Maven 項目;
  • 一個 Gradle 源集;
  • 一次 <kotlinc> Ant 任務執行所編譯的一套文件。
修飾符 類成員 頂層聲明
public 全部地方可見 全部地方可見
internal 模塊中可見 模塊中可見
protected 子類中可見 ---
private 類中可見 文件中可見

枚舉和when

聲明枚舉類型

enum class Color(val r: Int, val g: Int, val b: Int){
    RED(255, 0, 0), GREEN(0, 255, 0), BLUE(0, 0, 255)
    
    fun rgb() = (r * 256 + g) * 256 + b
}

when表達式

用when處理枚舉類型:

fun getName(color: Color) = 
    when (color) {
        Color.RED -> "Apple"
        Color.GREEN -> "Hat"
        Color.BLUE -> "Sky"
    }

顯式導入枚舉常量後:

import Color.*

fun getName(color: Color) = 
    when (color) {
        RED -> "Apple"
        GREEN -> "Hat"
        BLUE -> "Sky"
    }

能使用任何表達式做爲分支條件:

fun mix(c1: Color, c2: Color) = 
    when (setOf(c1, c2)) {
        setOf(RED, GREEN) -> "Apple"
        setOf(RED, BLUE) -> "Hat"
        setOf(GREEN, BLUE) -> "Sky"
        else -> throw Exception("Dirty color")
    }

若是沒有給when表達式提供參數, 那麼分支條件就是任意的布爾表達式。

智能轉換

interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr

fun eval(e: Expr): Int =
    if (e is Num) {
        // 這個塊內e被智能轉換成Num 所以不須要下面這行代碼
        // val n = e as Num
        e.value
    } else if (e is Sum) {
        // 這個塊內e被只能轉換成Sum
        eval(e.right) + eval(e.left)
    } else {
        throw IllegalArgumentException("Unknown expression")
    }

fun main(args: Array<String>) {
    println(eval(Sum(Sum(Num(1), Num(2)), Num(4))))
}

// 輸出 7

代碼塊中最後的表達式就是結果, 在全部使用代碼塊並指望獲得結果的地方成立(除了常規函數).

when表達式代替if

fun eval(e: Expr): Int = 
    when (e) {
        is Num ->
            e.value
        is Sum ->
            eval(e.right) + eval(e.left)
        else ->
            throw IllegalArgumentException("Unknown expression")
    }

while循環和for循環

while循環

while循環語法與java中相同:

區間和數列

  • 區間是兩個值之間的間隔, 用 .. 運算符來表示區間(.. 運算符也能夠用做建立字符區間);
  • Kotlin的區間是包含的或者閉合的,第二個值始終是區間的一部分;
  • 若是能迭代一個區間中的全部值,這樣的區間就叫數列。
// 1到100
for (i in 1..100) {
    println(i)
}

// 100到1 步長爲2
for (i in 100 downTo 1 step 2) {
    println(i)
}

迭代map

val binaryReps = TreeMap<Char, String>()

for (c in 'A'..'F') {
    binaryReps[c] = Integer.toBinaryString(c.toInt())
}

for ((letter, binary) in binaryReps) {
    println("$letter = $binary")
}

用 in 檢查集合和區間的成員

fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'
fun recognize(c: Char) = when(c) {
    in '0'..'9' -> "It's a digit!"
    in 'a'..'z', in 'A'..'Z' -> "It's a letter!" 
    else -> "I don't know..."
}

異常

Kotlin的異常處理基本和Java相似,不一樣的是Kotlin的try和throw能做爲表達式使用:

val percentage = 
    if (number in 0..100)
        number
    else 
        throw IllegalArgumentException("$number is not between 0 and 100")

throw表達式的類型爲Nothing。

fun readNumber(reader: BufferedReader) {
    val number = try {
        Integer.parseInt(reader.readLine())
    } catch (e: NumberFormatException) {
        null
    }
    
    println(number)
}

try表達式的值語句主題的最後一個表達式的值, 若是捕獲到了異常,那catch塊的最後一個表達式就是結果。

空安全

Kotlin 的類型系統旨在從咱們的代碼中消除 NullPointerException 。NPE 的惟一可能的緣由
多是:

  • 顯式調用 throw NullPointerException() ;
  • 使用了下文描述的 !! 操做符;
  • 外部 Java 代碼致使的;
  • 對於初始化,有一些數據不一致(如一個未初始化的this用於構造函數的某個地方)。

可空類型與非空類型

Kotlin對可空類型顯式支持, 這是一種指出程序中哪些變量和屬性容許爲null的方式.
全部常見類型默認都是非空的, 除非用?標記爲可空.

var a: String = "abc"
a = null // 編譯錯誤
var a: String? = "abc"
a = null // OK

可空類型不能訪問類型的成員:

var a: String? = "abc"
println(a.length) // 編譯錯誤

可是在檢查非空後能夠訪問成員:

var a: String? = "abc"
if (a != null)
    println(a.length)
    
// 以及...
println(when {
    a != null -> a.length
    else -> return
})

安全調用

安全調用操做符 ?. 是處理可空類型最安全有效的一種工具, 他把一次null檢查和一次方法調用合併成一個操做.

var a: String? = "abc"
println(a?.toUpperCase())
println(a?.length)

若是調用的是非空值的方法, 方法會正常執行; 若是值是null, 方法不會執行, 表達式的值爲null; 表達式的類型爲可空類型.

null合併運算符(Elvis運算符)

Elvis運算符 ?: 第一個運算參數不爲null, 結果就是第一個參數, 不然結果是第二個參數.

var a: String? = null
println(a ?: "abc")
println(a ?: throw IllegalArgumentException())
println(a ?: return)

非空斷言

你能夠用 !! 來告訴編譯器這個值不會爲null, 並準備好了接收NPE異常.

var a: String? = null
var b: String = a!! //會在這裏拋出NPE
println(b.length)

安全轉換

as? 運算符嘗試把值轉換成指定類型, 若是值不是合適的類型就返回null.

class Person(val firstName: String, val lastName: String) {
   override fun equals(o: Any?): Boolean {
      val otherPerson = o as? Person ?: return false

      return otherPerson.firstName == firstName &&
             otherPerson.lastName == lastName
   }

   override fun hashCode(): Int =
      firstName.hashCode() * 37 + lastName.hashCode()
}

可空類型的擴展

fun verifyUserInput(input: String?) {
    if (input.isNullOrBlank()) {
        println("Please fill in the required fields")
    }
}

fun main(args: Array<String>) {
    verifyUserInput(" ")
    verifyUserInput(null)
}
fun String?.isNullOrBlank(): Boolean = this == null || this.isBlank()

基本數據類型和其餘基本類型

在 Kotlin 中, 全部東西都是對象, 但一些類型能夠有特殊的內部表示——例如數字, 字符和布爾值能夠在運行時表示爲原生類型值.

數字

Kotlin 處理數字在某種程度上接近 Java, 可是並不徹底相同. 例如對於數字沒有隱式拓寬轉換, 另外有些狀況的字面值略有不
同.在kotlin中字符不是數字.

123L // 十進制Long
0x0F // 十六進制
0b00001011 // 二進制
123.5 // Double
123.5e10 // Double
123.5f // Float

不支持8進制

從1.1起, kotlin的數字字面值中能夠加入下劃線來使數字更易讀:

val oneMillion = 1_000_000

表示方式

在Java平臺下數字會物理存儲爲JVM的原生類型; 在須要可空引用或泛型時, 會將數字裝箱.

val a: Int = 10000
print(a === a)    // true
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA == anotherBoxedA)  // true
print(boxedA === anotherBoxedA) // false

Kotlin的相等性

  • 引用相等, 由===檢查;
  • 結構相等, 由equals檢查.

a == b可被翻譯爲a?.equals(b) ?: (b === null)
== 可被重載而 === 不行

顯式轉換

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

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

運算

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

沒有特殊字符來表示位運算,只能中綴方式調用命名函數:

val x = (1 shl 2) and 0x000FF000
  • shl(bits) – 有符號左移 ( << )
  • shr(bits) – 有符號右移 ( >> )
  • ushr(bits) – 無符號右移 ( >>> )
  • and(bits) – 位與
  • or(bits) – 位或
  • xor(bits) – 位異或
  • inv() – 位非

字符

字符用Char類表示;字符不能被看成數字, 可是可用toInt()轉換爲Int; 須要可空引用時也會被裝箱.

布爾

布爾用Boolean類表示, 若須要可空引用布爾會被裝箱.

數組

數組使用Array類來表示, 定義了get和set函數(在操做符重載中對應[]), size以及成員函數.

arrayOf(1, 2, 3) // array [1, 2, 3]。
arrayOfNulls(3) // array [null, null, null]
Array(5, { i -> (i * i).toString() }) // ["0", "1", "4", "9", "16"]

字符串

字符串使用String 表示; 字符串中的字符可用索引訪問: s[i]; 可用for遍歷字符串:

for (c in str) {
    println(c)
}

原生字符串 使用"""分界符括起來,內部沒有轉義而且能夠包含換行和任何其餘字符:

val text = """
    for (c in "foo")
    print(c)
    """
println(text)

val text2 = """
    |for (c in "foo")
    |print(c)
    """.trimMargin() // 用`.trimMargin()`去除前導空格
println(text2)

val text3 = """
    >for (c in "foo")
    >print(c)
    """.trimMargin(">") // 用`.trimMargin()`去除前導空格
println(text3)

/* output: 

    for (c in "foo")
    print(c)
    
for (c in "foo")
print(c)

for (c in "foo")
print(c)
 */

Any和Any?

相似Object, Any是全部非空類型的超類型. 它除了equals(),hashCode()和toString() 外沒有任何成員. 若是須要調用Object的方法, 能夠把值轉換成java.lang.Object來調用.

Unit類型

與Java中的void功能同樣, 但Unit是一個完備的類型, 能夠做爲類型參數.

interface Processor<T> {
    fun process(): T
}

class NoResultProcessor : Processor<Unit> {
    override fun process() {
        // ...
        // 不須要return
    }
}

Nothing類型

Nothing類型沒有值, 它用於標記永遠不可能到達的地方. 能夠用Nothing來標記一個永遠不可能返回的函數.

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

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

val x = null // 「x」具備類型 `Nothing?`
val l = listOf(null) // 「l」具備類型 `List<Nothing?>

集合和數組

只讀集合與可變集合

Kotlin中並無本身的集合類, 它的集合類和Java中徹底相同; Kotlin把訪問集合數據的接口和修改集合數據的接口分開了.

Kotlin 的 List 類型是一個提供只讀操做如 size 、get 等的接口。和 Java 相似,它繼承自 Collection 進而繼承自 Iterable 。改變 list 的方法是由 MutableList 加入的。這一模式一樣適用於 Set /MutableSet 及 Map<K, out V>/MutableMap<K, V> 。

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泛型定義與Java相似. Kotlin中沒有通配符,它有兩個其餘的東西:聲明處型變(declaration-site variance)和類型投影(type projections).

List<String> strs = new ArrayList<String>();
List<Object> objs = strs; // Java中禁止這樣

聲明處型變

協變: out操做符標註類型參數只用作在方法中返回, 而不會在方法的參數中出現:

abstract class Source<out T> { //Source 被聲明爲在T上協變
    abstract fun nextT(): T
}
fun demo(strs: Source<String>) {
    val objects: Source<Any> = strs 
    // ……
}

逆變: in操做符標註類型參數只能用在方法參數中, 而不能出如今返回值中:

abstract class Comparable<in T> {
    abstract fun compareTo(other: T): Int
}
fun demo(x: Comparable<Number>) {
    val y: Comparable<Double> = x // OK!
}

消費者 in, 生產者 out!

類型投影

使用處的型變.
例如Array 這樣的類, 不能在參數類型的聲明處限制只能返回T, 能夠在使用的時候限制它:

fun copy(from: Array<out Any>, to: Array<Any>) {
    // ……
}

fun fill(dest: Array<in String>, value: String) {
    // ……
}

星投影

當你不知道泛型的信息時, 可使用星投影:

fun printFirst(list: List<*>) {
   if (list.isNotEmpty()) {
       println(list.first())
   }
}

註解

聲明註解,要將 annotation 修飾符放在類的前面

annotation class Fancy

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

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

註解能夠有接受參數的構造函數:

annotation class Special(val why: String)
@Special("example") class Foo {}

容許的參數:

  • 對應於 Java 原生類型的類型(Int、 Long等);
  • 字符串;
  • 類( Foo::class );
  • 枚舉;
  • 其餘註解;
  • 上面已列類型的數組。

註解參數不能有可空類型,由於 JVM 不支持將 null 做爲註解屬性的值存儲。

若是註解用做另外一個註解的參數,則其名稱不以 @ 字符爲前綴:

annotation class ReplaceWith(val expression: String)
annotation class Deprecated(
val message: String,
val replaceWith: ReplaceWith = ReplaceWith(""))
@Deprecated("This function is deprecated, use === instead", ReplaceWith("this === othe
r"))

若是須要將一個類指定爲註解的參數,請使用 Kotlin 類 (KClass)。Kotlin 編譯器會自動將
其轉換爲 Java 類,以便 Java 代碼可以正常看到該註解和參數:

import kotlin.reflect.KClass
annotation class Ann(val arg1: KClass<*>, val arg2: KClass<out Any?>)
@Ann(String::class, Int::class) class MyClass

反射

Kotlin可使用兩種反射API, Java的反射和Kotlin的反射.

Kotlin的反射API在kotlin.reflect下, Kotlin反射能提供Java中沒有的信息(屬性和可空類型); Kotlin反射API沒有僅限於Kotlin類.

使用Kotlin的反射API須要添加kotlin-reflect的jar

類引用

KClass表明了一個類, 對應 java.lang.Class .
可使用MyClass::class的寫法來得到KClass實例; 用MyClass::class.java來得到Java中的class對象; 在運行時, 可使用javaClass來得到Java類, 再訪問.kotlin擴展屬性:

import kotlin.reflect.memberProperties

class Person(val name: String, val age: Int)

fun main(args: Array<String>) {
    val person = Person("Alice", 29)
    val kClass = person.javaClass.kotlin // 或者person::class
    println(kClass.simpleName)
    kClass.memberProperties.forEach { println(it.name) }
}

函數引用

使用 :: 來得到一個命名函數的引用:

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

val numbers = listOf(1, 2, 3)

println(numbers.filter(::isOdd)) // ::isOdd 是函數類型(Int) -> Boolean的一個值
// 輸出 [1, 3]

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

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: (Int) -> Boolean = ::isOdd

引用類的成員函數或者成員屬性,或者extension函數,須要加上類名,如String::toCharArray.

屬性引用

val x = 1
fun main(args: Array<String>) {
    // 
    println(::x.get()) // 輸出 "1"
    println(::x.name) // 輸出 "x"
}

::x 類型爲KProperty ; 若是x是可變屬性, 則他的類型爲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"
}

構造函數引用

構造函數能夠像方法和屬性那樣引用。他們能夠用於期待這樣的函數類型對象的任何地方:
它與該構造函數接受相同參數而且返回相應類型的對象。

class Foo

fun function(factory : () -> Foo) {
    val x : Foo = factory()
}

function(::Foo)

Lambda

簡化Lambda表達式

args.forEach({ 
    element -> println(element) 
}) 

args.forEach{ 
    println(it) 
} 


args.forEach(::println)
  • 最後一個Lambda能夠移參數列表的括號
  • 只有一個Lambda,小括號可省略
  • Lambda 只有一個參數可默認爲 it
  • 入參、返回值與形參一致的函數能夠用函數引用的方式做爲實參傳入

返回和跳轉

Kotlin 有三種結構化跳轉表達式, 這些表達式的類型是Nothing:

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

在 Kotlin 中任何表達式均可以用標籤(表示符後加@, 好比abc@)來標記。加上標籤後, 就能夠用標籤來限制break和continue.

loop@ for (i in 1..100) {
    for (j in 1..100) {
        if (……) break@loop
    }
}

break 跳轉到恰好位於該標籤指定的循環後面的執行點。 continue 繼續標籤指定的循環的下一次迭代。

若是咱們須要從Lambda中返回, 必須加標籤來限制return:

args.forEach forEachBlock@{ 
    if(it == "q") return@forEachBlock 
    println(it) 
}

Gradle + Spring Boot

能夠去start.spring.io生成一個示例項目

  1. 新建一個gradle項目:

  1. 添加所需插件和依賴, 最終gradle.build以下:
group 'utry'
version '1.0-SNAPSHOT'

buildscript {
    ext.kotlin_version = '1.1.4-2'
    ext.springboot_version = '1.5.2.RELEASE'

    repositories {
        // 倉庫的阿里雲鏡像
        maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
        //jcenter()
        //mavenCentral()
    }
    dependencies {
        // Kotlin Gradle插件
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        // Kotlin整合SpringBoot的默認無參構造函數,默認把全部的類設置open類插件
        classpath("org.jetbrains.kotlin:kotlin-allopen:$kotlin_version")
        classpath("org.jetbrains.kotlin:kotlin-noarg:$kotlin_version")
        // SpringBoot Gradle插件
        classpath("org.springframework.boot:spring-boot-gradle-plugin:$springboot_version")
    }
}

apply plugin: 'kotlin'
apply plugin: 'war'
//Kotlin-spring 編譯器插件,它根據 Spring 的要求自動配置全開放插件。
apply plugin: 'kotlin-spring'
apply plugin: 'org.springframework.boot'

repositories {

    // 倉庫的阿里雲鏡像
    maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
    //jcenter()
    //mavenCentral()
}

dependencies {

    // https://mvnrepository.com/artifact/io.springfox/springfox-swagger2
    compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.7.0'
    // https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui
    compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.7.0'
    
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-web'
    compile group: 'org.mybatis.spring.boot', name: 'mybatis-spring-boot-starter', version: '1.3.1'
    compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
    testCompile group: 'junit', name: 'junit', version: '4.11'
    compile('mysql:mysql-connector-java:5.1.13')

}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
  1. 建立Spring Boot配置類和main函數:
@SpringBootApplication
@EnableSwagger2
class PostApplication {
    @Bean
    fun createRestApi(): Docket {
        return Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("cn.utry"))
                .paths(PathSelectors.any())
                .build()
    }

    private fun apiInfo(): ApiInfo {
        return ApiInfoBuilder()
                .title("簡單的Demo")
                .build()
    }
}

fun main(args: Array<String>) {
    SpringApplication.run(PostApplication::class.java, *args)
}
  1. Controller示例:
@RestController
@RequestMapping("/post")
class PostController {

    @Autowired
    lateinit var postService: PostService

    @RequestMapping("/", method = arrayOf(RequestMethod.POST))
    fun submit(content: String) = postService.submit(content)

    @RequestMapping("/", method = arrayOf(RequestMethod.GET))
    fun query() = postService.query()
}

kotlin的生態

測試

  • KotlinTest should風格.
  • Spek 屬於Kotlin的BDD風格的測試框架. JetBrains發起, 現由社區維護.
// KotlinTest示例 
s should startWith("kot")

JSON序列化

  • Jackson;
  • Kotson GSON的包裝器.

Web應用

  • Ktor JetBrains的研究項目.
  • Kara 最初的Kotlin Web框架.

數據庫

Exposed, sql生成框架.

//聲明一張表
object Country : Table() {
    val id = integer("id").autoIncrement().primaryKey() //Column類型
    val name = varchar("name", 50)
}

SchemaUtils.create(Country)
相關文章
相關標籤/搜索