Kotlin知識點總結與初寫時的一些建議

本文是在學習和使用kotlin時的一些總結與體會,一些代碼示例來自於網絡或Kotlin官方文檔,持續更新...html

對象相關

  • 對象表達式:至關於Java匿名類部類,在使用的地方被當即執行:java

    val a = 10
    
    val listener = object : Info("submit"),IClickListener {
        override fun doClick() {
            println("a:$a")
        }
    
    }
    
    listener.doClick() // 打印 a:10
    //有時候咱們只是須要一個沒有父類的對象,咱們能夠這樣寫:
    val adHoc = object {
        var x: Int = 0
        var y: Int = 0
    }
    
    print(adHoc.x + adHoc.y)
    //像 java 的匿名內部類同樣,對象表達式能夠訪問閉合範圍內的變量 (和 java 不同的是,這些變量不用是 final 修飾的)
    fun countClicks(window: JComponent) {
        var clickCount = 0
        var enterCount = 0
        window.addMouseListener(object : MouseAdapter() {
            override fun mouseClicked(e: MouseEvent) {
                clickCount++
            }
            override fun mouseEntered(e: MouseEvent){
                enterCount++
            }
        })
    }
    複製代碼
  • 對象申明:Kotlin 中咱們能夠方便的經過對象聲明來得到一個單例,對象聲明是延遲加載的, 在第一次使用的時候被初始化,對象聲明不是一個表達式,不能用在賦值語句的右邊,對象聲明不能在局部做用域(即直接嵌套在函數內部),可是它們能夠嵌套到其餘對象聲明或非內部類中,node

    object MyInfo: Info("submit"),IClickListener {
    
        override fun doClick() {
            println("MyInfo do click, $text") // Log: MyInfo do click, , submit
        }
    }
    
    fun main(args: Array<String>) {
    
        MyInfo.doClick()
    }
    //當對象聲明在另外一個類的內部時,這個類的實例並不能直接訪問對象申明內部,而只能經過類名來訪問,一樣該對象也不能直接訪問到外部類的方法和變量
    class Site {
        var name = "菜鳥教程"
        object DeskTop{
            var url = "www.runoob.com"
            fun showName(){
                print{"desk legs $name"} // 錯誤,不能訪問到外部類的方法和變量
            }
        }
    }
    fun main(args: Array<String>) {
        var site = Site()
        site.DeskTop.url // 錯誤,不能經過外部類的實例訪問到該對象
        Site.DeskTop.url // 正確
    }
    複製代碼
  • 伴隨(生)對象:至關於靜態內部類+該類的靜態屬性,所在的類被加載,伴生對象被初始化(和 Java 的靜態初始是對應):python

    class Books(var name: String, val page: Int) {
        companion object ComBooks{
            val a : Int = 10
            fun doNote() {
                println("do note")
            }
        }
    }
    
    fun main(args: Array<String>) {
        Books.ComBooks.doNote()
    
        println("Book.a = ${Books.ComBooks.a}")
    
        println("-------------")
    
        Books.doNote()
    
    }
    
    // Log
    do note
    Book.a = 10
    -------------
    do note
    //伴隨對象的成員能夠經過類名作限定詞直接使用:
    class MyClass {
        companion object Factory {
            fun create(): MyClass = MyClass()
        }
    }
    val instance = MyClass.create()
    //在使用了 companion 關鍵字時,伴隨對象的名字能夠省略:
    class MyClass {
        companion object {
    
        }
    }
    //儘管伴隨對象的成員很像其它語言中的靜態成員,但在運行時它們任然是真正類的成員實例,好比能夠實現接口:
    interface Factory<T> {
        fun create(): T
    }
    
    class MyClass {
        companion object : Factory<MyClass> {
            override fun create(): MyClass = MyClass()
        }
    }
    //若是你在 JVM 上使用 @JvmStatic 註解,你能夠有多個伴隨對象生成爲真實的靜態方法和屬性
    複製代碼

屬性字段相關

  • 備用字段:Kotlin中不能有field,但在自定義getter/setter的時候須要直接訪問屬性而不是又經過getter/settter來取值賦值(循環調用)。Kotlin自動提供一個備用字段(field),經過它能夠直接訪問屬性,沒有使用備用字段時不生成備用字段(使用了setter就會生成),:算法

    //使用field關鍵字
    public var fieldProp = ""
        get() = field
        set(value) {
            field = value;
        }
    //不生成:
    val isEmpty: Boolean
        get() = this.size == 0
    //生成:
    val Foo.bar = 1
    複製代碼
  • 備用屬性:功能與備用字段相似。:express

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

    Kotlin能夠像python(@property)同樣把方法變成屬性調用,Kotlin是定義一個屬性複寫get()方法返回某個對象中其餘的計算出來的值。編程

編譯時常量

  • 至關於java static finial xxx,而val 只是fInal ,一個編譯時常量,一個運行時常量。使用const必須:後端

    • 在kt文件中(類以外,Top-level)或在object{}中
    • 必須是基本類型或String
    • 必須沒有自定義getter
    const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
    @Deprected(SUBSYSTEM_DEPRECATED) fun foo() { ... }
    複製代碼

延遲初始化屬性

  • 當定義屬性時沒有使用 ? ,那麼說明是一個非空屬性,這時必需要初始化,若是想在後面的方法中再去賦值要加上lateinit。:api

    public class MyTest {
        lateinit var subject: TestSubject
    
        @SetUp fun setup() {
            subject = TestSubject()
        }
    
        @Test fun test() {
            subject.method()
        }
    }
    //這個修飾符只可以被用在類的 var 類型的可變屬性定義中,不能用在構造方法中.而且屬性不能有自定義的 getter 和 setter訪問器.這個屬性的類型必須是非空的,一樣也不能爲一個基本類型.在一個lateinit的屬性初始化前訪問他,會致使一個特定異常,告訴你訪問的時候值尚未初始化
    複製代碼

複寫屬性

  • 屬性可複寫,在主構造函數中就可以使用override關鍵字做爲屬性聲明。

代理(委託)模式

  • 類代理: 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
    複製代碼

  • 代理屬性: 所謂的委託屬性,就是對其屬性值的操做再也不依賴於其自身的getter()/setter()方法,是將其託付給一個代理類,從而每一個使用類中的該屬性能夠經過代理類統一管理,不再用在每一個類中,對其聲明重複的操做方法。語法:

    val/var <property name>: <Type> by <expression>
    //var/val:屬性類型(可變/只讀)
    //name:屬性名稱
    //Type:屬性的數據類型
    //expression:代理類
    複製代碼

    使用場景:

    • 延遲加載屬性(lazy property): 屬性值只在初次訪問時纔會計算
    • 可觀察屬性(observable property): 屬性發生變化時, 能夠向監聽器發送通知
    • 將多個屬性保存在一個 map 內, 而不是保存在多個獨立的域內

    Kotlin標準庫中已實現的代理:

    • 延遲加載(Lazy):lazy()是一個函數, 接受一個Lambda表達式做爲參數, 返回一個Lazy類型的實例,這個實例能夠做爲一個委託, 實現延遲加載屬性(lazy property): 第一次調用 get() 時, 將會執行 lazy() 函數受到的Lambda 表達式,而後會記住此次執行的結果, 之後全部對 get() 的調用都只會簡單地返回之前記住的結果:

      val no: Int by lazy {
          200
      }
      
      val c = 200
      
      fun main(args: Array<String>) {
      
          val b = 200
      
          println(no) // Log : 200
          println(no) // Log : 200
      }
      
      複製代碼

      注意:

      • var類型屬性不能設置爲延遲加載屬性,由於在lazy中並無setValue(…)方法
      • lazy操做符是線程安全的。若是在不考慮多線程問題或者想提升更多的性能,也可使用 lazy(LazyThreadSafeMode.NONE){ … },lazy的三個參數爲:
        • SYNCHRONIZED:鎖定,用於確保只有一個線程能夠初始化[Lazy]實例。
        • PUBLICATION:初始化函數能夠在併發訪問未初始化的[Lazy]實例值時調用幾回,,但只有第一個返回的值將被用做[Lazy]實例的值。
        • NONE:沒有鎖用於同步對[Lazy]實例值的訪問; 若是從多個線程訪問實例,是線程不安全的。此模式應僅在高性能相當重要,而且[Lazy]實例被保證永遠不會從多個線程初始化時使用。
    • 可觀察屬性(Observable):Delegates.observable() 函數接受兩個參數: 第一個是初始化值, 第二個是屬性值變化事件的響應器(handler).這種形式的委託,採用了觀察者模式,其會檢測可觀察屬性的變化,當被觀察屬性的setter()方法被調用的時候,響應器(handler)都會被調用(在屬性賦值處理完成以後)並自動執行執行的lambda表達式,同時響應器會收到三個參數:被賦值的屬性, 賦值前的舊屬性值, 以及賦值後的新屬性值。:

      var name: String by Delegates.observable("wang", {
          kProperty, oldName, newName ->
          println("kProperty:${kProperty.name} | oldName:$oldName | newName:$newName")
      })
      
      fun main(args: Array<String>) {
      
          println("name: $name") // Log:nam:wang
      
          name = "zhang" // Log:kProperty:name | oldName:wang | newName:zhang
      
          name = "li" // Log:kProperty:name | oldName:zhang | newName:li
      }
      //Delegates.observable(wang, hanler),完成了兩項工做,一是,將name初始化(name=wang);二是檢測name屬性值的變化,每次變化時,都會打印其賦值前的舊屬性值, 以及賦值後的新屬性值。
      ​```
      複製代碼
    • Vetoable:Delegates.vetoable()函數接受兩個參數: 第一個是初始化值, 第二個是屬性值變化事件的響應器(handler),是可觀察屬性(Observable)的一個特例,不一樣的是在響應器指定的自動執行執行的lambda表達式中在保存新值以前作一些條件判斷,來決定是否將新值保存。:

      var name: String by Delegates.vetoable("wang", {
          kProperty, oldValue, newValue ->
          println("oldValue:$oldValue | newValue:$newValue")
          newValue.contains("wang")
      })
      
      fun main(args: Array<String>) {
      
          println("name: $name")
          println("------------------")
          name = "zhangLing" 
          println("name: $name") 
          println("------------------")
          name = "wangBing" 
          println("name: $name") 
      }
      
      //Log 
      name: wang
      ------------------
      oldValue:wang | newValue:zhangLing
      name: wang
      ------------------
      oldValue:wang | newValue:wangBing
      name: wangBing
      ​```
      複製代碼
    • Not Null:在實際開發時,咱們可能會設置可爲null的var類型屬性,在咱們使用它時,確定是對其賦值,假如不賦值,必然要報NullPointException.一種解決方案是,咱們能夠在使用它時,在每一個地方無論是否是null,都作null檢查,這樣咱們就保證了在使用它時,保證它不是null。這樣無形當中添加了不少重複的代碼。在Kotlin中,用委託能夠不用去寫這些重複的代碼,Not Null委託會含有一個可null的變量並會在咱們設置這個屬性的時候分配一個真實的值。若是這個值在被獲取以前沒有被分配,它就會拋出一個異常。

      class App : Application() {
          companion object {
              var instance: App by Delegates.notNull()
          } 
      
          override fun onCreate() {
              super.onCreate()
              instance = this
          }
      }
      複製代碼
    • 將多個屬性保存在一個map內:使用Gson解析Json時,能夠獲取到相應的實體類的實例,固然該實體類的屬性名稱與Json中的key是一一對應的。在Kotlin中,存在這麼一種委託方式,類的構造器接受一個map實例做爲參數,將map實例自己做爲屬性的委託,屬性的名稱與map中的key是一致的,也就是意味着咱們能夠很簡單的從一個動態地map中建立一個對象實例:

      class User(val map: Map<String, Any?>) {
          val name: String by map
          val age: Int by map
      }
      
      fun main(args: Array<String>) {
      
          val user = User(mapOf(
                  "name" to "John Doe",
                  "age" to 25
          ))
      
          println(user.name) // 打印結果爲: "John Doe"
          println(user.age) // 打印結果爲: 25
      }
      //委託屬性將從這個 map中讀取屬性值(使用屬性名稱字符串做爲 key 值)。
      //若是不用只讀的 Map , 而改用值可變的 MutableMap , 那麼也能夠用做 var 屬性的委託。:
      class User(val map: MutableMap<String, Any?>) {
          val name: String by map
          val age: Int by map
      }
      
      fun main(args: Array<String>) {
      
          var map:MutableMap<String, Any?> = mutableMapOf(
                  "name" to "John Doe",
                  "age" to 25)
      
          val user = User(map)
      
          println(user.name) // 打印結果爲: "John Doe"
          println(user.age) // 打印結果爲: 25
      
          println("--------------")
          map.put("name", "Green Dao")
          map.put("age", 30)
      
          println(user.name) // 打印結果爲: Green Dao
          println(user.age) // 打印結果爲: 30
      
      }
      複製代碼
    • 屬性委託的前提條件:getValue(),setValue()。自定義委託必需要實現:ReadOnlyProperty和ReadWriteProperty。取決於咱們被委託的對象是val仍是var,如:

      public interface ReadWriteProperty<in R, T> {
          public operator fun getValue(thisRef: R, property: KProperty<*>): T
          public operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
      }
      //定義一個NotNullVar
      private class NotNullVar<T: Any>() : ReadWriteProperty<Any?, T> {
          private var value: T? = null
      
          public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
              return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.")
          }
      
          public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
              this.value = value
          }
      }
      //第一個thisRef表示持有該對象的對象,
      //第二個參數 property 是該值的類型,
      //第三個參數 value 就是屬性的值了
      複製代碼

密封類

  • 密封類:類的擴展,但每一個枚舉常量只存在一個實例,而密封類的一個子類能夠有可包含狀態的多個實例,雖然密封類也能夠有子類,可是全部子類都必須在與密封類自身相同的文件中聲明,間接的子類不受限制。密封類是自身抽象的,它不能直接實例化並能夠有抽象(abstract)成員。密封類不容許有非-private 構造函數(其構造函數默認爲 private)。使用密封類的關鍵好處在於使用 when 表達式 的時候,可以驗證語句覆蓋了全部狀況,就不須要爲該語句再添加一個 else 子句了:

    sealed class Expr
    data class Const(val number: Double) : Expr()
    data class Sum(val e1: Expr, val e2: Expr) : Expr()
    object NotANumber : Expr()
    
    fun eval(expr: Expr): Double = when(expr) {
        is Const -> expr.number
        is Sum -> eval(expr.e1) + eval(expr.e2)
        NotANumber -> Double.NaN
        // 再也不須要 `else` 子句,由於咱們已經覆蓋了全部的狀況
    }
    複製代碼

接口

  • 與java8相似可有抽象方法與實現方法,不可保存狀態,屬性必須是抽象的或惟一值的(無備用屬性)。

擴展

  • 不須要在類中去添加方法,在外部就能夠給任何地方的類添加咱們想要的方法,替換掉java中的FileUtil,xxxUtil等如:

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

    擴展是被靜態解析的:擴展實際上並無修改它所擴展的類,只是讓這個類的實例對象可以經過"."調用新的函數。須要強調的是擴展函數是靜態分發的,舉個例子,它們並非接受者類型的虛擬方法。這意味着擴展函數的調用是由發起函數調用的表達式的(對象)類型決定的,而不是在運行時動態得到的表達式的(對象)類型決定。好比:

    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().foo()
    //輸出"member",而不是"extension"
    //能夠經過不一樣的函數簽名的方式重載函數的成員函數:
    fun C.foo(i:Int) { println("extention") }
    C().foo(1)
    //輸出 「extentions」。
    複製代碼

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

    package foo.bar
    fun Baz.goo() { ... }
    //爲了在除聲明的包外使用這個擴展,咱們須要在別的文件中使用時導入:
    //-------------------------------------------------//
    package com.example.usage
    
    import foo.bar.goo // 導入全部名字叫 "goo" 的擴展
    
                        // 或者
    
    import foo.bar.* // 導入foo.bar包下得全部數據
    
    fun usage(baz: Baz) {
        baz.goo()
    }
    複製代碼

  • 函數擴展:

    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
    }
    //this 關鍵字對應接收者對象(MutableList<T>)
    //使用:
    val l = mutableListOf(1, 2, 3)
    l.swap(0, 2)// 在 `swap()` 函數中 `this` 持有的值是 `l`
    複製代碼
  • 可空的接收者: 使用空接收者類型進行定義。這樣的擴展使得,即便是一個空對象仍然能夠調用該擴展,而後在擴展的內部進行 this == null 的判斷。這樣你就能夠在 Kotlin 中任意調用 toString() 方法而不進行空指針檢查:空指針檢查延後到擴展函數中完成:

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

    T.因此擴展是用.來使用的,如Kotlin的to函數就是A to B 以空格使用。

  • 屬性擴展

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

    //正確:
    val <T> List<T>.lastIndex:  Int
        get() = size-1
    //錯誤:
    val Foo.bar = 1 //error: initializers are not allowed for extension properties
    複製代碼

  • 伴隨對象擴展

    class MyClass {
        companion object {} 
    }
    fun MyClass.Companion.foo(){
    
    }
    //調用
    MyClass.foo()
    複製代碼

數據類

咱們常常建立一個只保存數據的類。在這樣的類中一些函數只是機械的對它們持有的數據進行,如從服務端返回的Json字符串對象映射成Java類。data類使用:

data class 類名(var param1 :數據類型,...){}
data class 類名 可見性修飾符 constructor(var param1 : 數據類型 = 默認值,...)
//data爲聲明數據類的關鍵字,必須書寫在class關鍵字以前。
//在沒有結構體的時候,大括號{}可省略。
//構造函數中必須存在至少一個參數,而且必須使用val或var修飾。這一點在下面數據類特性中會詳細講解。
//參數的默認值無關緊要。(若要實例一個無參數的數據類,則就要用到默認值)
// 定義一個名爲Person的數據類:
data class Preson(var name : String,val sex : Int, var age : Int)
複製代碼

data類必須知足的條件:

  • 主構造函數須要至少有一個參數
  • 主構造函數的全部參數須要標記爲 val 或 var;
  • 數據類不能是抽象、開放、密封或者內部的;
  • 數據類是能夠實現接口的,如(序列化接口),同時也是能夠繼承其餘類的,如繼承自一個密封類。

約定俗成的規定:當構造函數中的參過多時,爲了代碼的閱讀性,一個參數的定義佔據一行。

編輯器爲咱們作的事情:

  • 生成equals()函數與hasCode()函數
  • 生成toString()函數,由類名(參數1 = 值1,參數2 = 值2,....)構成
  • 由所定義的屬性自動生成component1()、component2()、...、componentN()函數,其對應於屬性的聲明順序。
  • copy()函數。修改部分屬性,可是保持其餘不變。

copy函數的使用:

data class User(val name : String, val pwd : String)

val mUser = User("kotlin","123456")
println(mUser)
val mNewUser = mUser.copy(name = "new Kotlin")
println(mNewUser)
複製代碼

標準庫提供的data類: Pair 和 Triple,源碼以下:

@file:kotlin.jvm.JvmName("TuplesKt")
package kotlin

// 這裏去掉了源碼中的註釋
public data class Pair<out A, out B>(
        public val first: A,
        public val second: B) : Serializable {
    
    // toString()方法
    public override fun toString(): String = "($first, $second)"
}

// 轉換
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

// 轉換成List集合
public fun <T> Pair<T, T>.toList(): List<T> = listOf(first, second)

// 這裏去掉了源碼中的註釋
public data class Triple<out A, out B, out C>(
        public val first: A,
        public val second: B,
        public val third: C ) : Serializable {

    // toString()方法
    public override fun toString(): String = "($first, $second, $third)"
}

// 轉換成List集合
public fun <T> Triple<T, T, T>.toList(): List<T> = listOf(first, second, third)

複製代碼

泛型

  • 兩種型變:

    • 協變:當A ≦ B時,若是有f(A) ≦ f(B),那麼f叫作協變;

    • 逆變:當A ≦ B時,若是有f(B) ≦ f(A),那麼f叫作逆變;

      其他爲不變。

    協變,逆變,不變來原於子類能夠安全的向上轉型爲父類。

  • 不變(java的泛型是不變的):

    ArrayList<Number> list = new ArrayList<Integer>(); //type mismatch 
    複製代碼
  • 協變:

    List<? extends Number> list001 = new ArrayList<Integer>();  
    List<? extends Number> list002 = new ArrayList<Float>(); 
    Number n1=list001.get(0);
    Number n2=list002.get(0);
    複製代碼

    ​ 「? extends Number」則表示通配符」?」的上界爲Number,換句話說就是,「? extends Number」能夠表明Number或其子類,但表明不了Number的父類(如Object),由於通配符的上界是Number。因而有「? extends Number」 ≦ Number,則List<? extends Number> ≦ List< Number >。可是這裏不能向list00一、list002添加除null之外的任意對象。能夠這樣理解一下,List能夠添加Interger及其子類,List能夠添加Float及其子類,List、List都是List<? extends Animal>的子類型,若是能將Float的子類添加到List<? extends Animal>中,就說明Float的子類也是能夠添加到List中的,顯然是不可行。故java爲了保護其類型一致,禁止向List<? extends Number>添加任意對象,不過卻能夠添加null。

  • 逆變:

    List<? super Number> list = new ArrayList<>();  
    List<? super Number> list001 = new ArrayList<Number>();  
    List<? super Number> list002 = new ArrayList<Object>();  
    list001.add(new Integer(3));  
    list002.add(new Integer(3));  
    複製代碼

    「? super Number」 則表示通配符」?」的下界爲Number。爲了保護類型的一致性,由於「? super Number」能夠是Object或其餘Number的父類,因沒法肯定其類型,也就不能往List<? super Number >添加Number的任意父類對象。可是能夠向List<? super Number >添加Number及其子類。

  • PECS(《Effective Java》,producer-extends, consumer-super):協變只能取(生產者),逆變只能寫(消費者),如java的幾個api:

    public class Stack<E>{  
        public Stack();  
        public void push(E e): public E pop();  
        public boolean isEmpty();  
    }  
    //push all
    // Wildcard type for parameter that serves as an E producer 
    public void pushAll(Iterable<? extends E> src) {  
        for (E e : src)  
            push(e);  
    } 
    //pop all
    // Wildcard type for parameter that serves as an E consumer 
    public void popAll(Collection<? super E> dst) {  
        while (!isEmpty())  
            dst.add(pop());  
    }  
    // java.util.Collections的copy方法
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {  
        int srcSize = src.size();  
        if (srcSize > dest.size())  
            throw new IndexOutOfBoundsException("Source does not fit in dest");  
      
        if (srcSize < COPY_THRESHOLD ||  
            (src instanceof RandomAccess && dest instanceof RandomAccess)) {  
            for (int i=0; i<srcSize; i++)  
                dest.set(i, src.get(i));  
        } else {  
            ListIterator<? super T> di=dest.listIterator();  
            ListIterator<? extends T> si=src.listIterator();  
            for (int i=0; i<srcSize; i++) {  
                di.next();  
                di.set(si.next());  
            }  
        }  
    }  
    複製代碼
  • Kotlin 用out ,in修飾符使協變與逆變使用起來更方便,out表示只生成,in表示只消費,稱之爲聲明處變型。這與 Java 中的使用處變型相反:

    abstract class Source<out T> {
        abstract fun nextT(): T
    }
    
    fun demo(strs: Source<String>) {
        val objects: Source<Any> = strs // This is OK, since T is an out-parameter
        // ...
    }
    //-------------------------//
    abstract class Comparable<in T> {
        abstract fun compareTo(other: T): Int
    }
    
    fun demo(x: Comparable<Number>) {
        x.compareTo(1.0) // 1.0 has type Double, which is a subtype of Number
        // Thus, we can assign x to a variable of type Comparable<Double>
        val y: Comparable<Double> = x // OK!
    }
    複製代碼
  • 類型投影

    使用處變型:類型投影。有些類 不能 限制它只返回 T,如Array,T既要返回又要在參數中消費:

    class Array<T>(val size: Int) {
        fun get(index: Int): T { /* ... */ }
        fun set(index: Int, value: T) { /* ... */ }
    }
    //這個類既不能是協變的也不能是逆變的,這會在必定程度上下降靈活性。考慮下面的函數形式:
    fun copy(from: Array<out Any>, to: Array<Any>) {
     // ...
    }
    複製代碼
  • 星投影

    有時你對類型參數一無所知,但任然想安全的使用它。保險的方法就是定一個該範型的投影,每一個該範型的正確實例都將是該投影的子類,Foo<*>

  • 範型約束

    上界:最經常使用的類型約束是上界,在 Java 中對應 extends關鍵字,這裏的上界只是約束在給泛型定型時要知足的條件。

    fun <T : Comparable<T>> sort(list: List<T>) {
        // ...
    }
    sort(listOf(1, 2, 3)) // OK. Int is a subtype of Comparable<Int>
    sort(listOf(HashMap<Int, String>())) // Error: HashMap<Int, String> is not a subtype of Comparable<HashMap<Int, String>>
    
    複製代碼

    默認的上界是 Any?。在尖括號內只能指定一個上界。若是要指定多種上界,須要用 where 語句指定:

    fun <T> cloneWhenGreater(list: List<T>, threshold: T): List<T>
        where T : Comparable,
              T : Cloneable {
      return list.filter { it > threshold }.map { it.clone() }
    }
    複製代碼

嵌套類

  • 嵌套類,與java的靜態內部類類似:

    class Outer {
        private val bar: Int = 1
        class Nested {
            fun foo() = 2
        }
    }
    
    val demo = Outer.Nested().foo() //==2
    複製代碼
  • 內部類,至關與java的內部類,持有一個外部類的引用,不能單獨使用:

    class Outer {
        private val bar: Int = 1
        inner class Inner {
            fun foo() = bar
        }
    }
    
    val demo = Outer().Inner().foo() //==1
    複製代碼
  • 匿名內部類,經過對象表達式建立:

    window.addMouseListener(object: MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
            // ...
        }
    
        override fun mouseEntered(e: MouseEvent) {
            // ...
        }
    })
    //若是對象是函數式的 java 接口的實例(好比只有一個抽象方法的 java 接口),你能夠用一個帶接口類型的 lambda 表達式建立它
    val listener = ActionListener { println("clicked") }
    複製代碼

函數

  • 函數參數

    • 標準參數:

      複製代碼

    fun powerOf(number: Int, exponent: Int) { ... }

    - 默認參數,函數參數能夠設置默認值,當調用函數時參數被忽略會使用默認值。這樣相比其餘語言能夠減小重載:
    
    ```kotlin
    fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size ) {
    ...
    }
    複製代碼
    • 命名參數:在調用函數時能夠用參數的命名來賦值參數。這對於那種有大量參數的函數很方便:

      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?) {
          ...
      }
      
      複製代碼
    • 變長參數:

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

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

      各類類型參數定義與使用與Python類似

  • 單表達式函數:

    //當函數只返回單個表達式時,大括號能夠省略並在 = 後面定義函數體:
    fun double(x: Int): Int = x*2
    //在編譯器能夠推斷出返回值類型的時候,返回值的類型能夠省略:
    fun double(x: Int) = x * 2
    複製代碼

  • 函數範圍

    Kotlin 中能夠在文件頂級聲明函數,這就意味者你不用像在Java,C#或是Scala同樣建立一個類來持有函數。除了頂級函數,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])
    }
    //局部函數甚至能夠返回到外部函數
    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
    }
    複製代碼
  • 成員函數

    跟java同樣,類中的成員。

  • 泛型函數

    跟java同樣。

  • 尾遞歸函數

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

    tailrec fun findFixPoint(x: Double = 1.0): Double 
        = if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))
    //使用 tailrec 修飾符必須在最後一個操做中調用本身。在遞歸調用代碼後面是不容許有其它代碼的,而且也不能夠在 try/catch/finall 塊中進行使用。當前的尾遞歸只在 JVM 的後端中能夠用
    複製代碼
  • 高階函數

    高階函數就是能夠接受函數做爲參數或者返回一個函數的函數:

    fun <T> lock(lock: Lock, body: () -> T ) : T {
        lock.lock()
        try {
            return body()
        }
        finally {
            lock.unlock()
        }
    }
    //body 是一個類型爲 () -> T 的函數,能夠這樣使用
    fun toBeSynchroized() = sharedResource.operation()
    val result = lock(lock, ::toBeSynchroized)
    //更方便的是傳一個字面函數(lambda表達式)
    val result = lock(lock, {
    sharedResource.operation() })
    //在 kotlin 中有一個約定,若是某一個函數的最後一個參數是函數,而且你向那個位置傳遞了一個 lambda 表達式,那麼,你能夠在括號外面定義這個 lambda 表達式:
    lock (lock) {
        sharedResource.operation()
    }
    //高階函數map:
    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 }.sortedBy{ it }.map{ it.toUpperCase() }
    
    複製代碼
  • 字面函數和函數表達式(lambda表達式),字面函數或函數表達式就是一個 "匿名函數",也就是沒有聲明的函數,但當即做爲表達式傳遞下去:

    max(strings, {a, b -> a.length < b.length })
    //max 函數就是一個高階函數,它接受函數做爲第二個參數。第二個參數是一個表達式因此本生就是一個函數,即字面函數。做爲一個函數,至關於:
    fun compare(a: String, b: String) : Boolean = a.length < b.length
    複製代碼

    若是隻有一個參數lambda中能夠不寫參數變量,直接用it表示參數,如:

    {it.length}
    複製代碼
  • Lambda表達式接收器:

    (函數字面量接收器,在定義高階函數參數時使用)是上面二者的結合——一個以指定接收器的擴展函數爲參數的高階函數。因此在咱們傳遞的Lambda表達式中咱們能夠直接訪問接收器的公共方法和屬性(在接受器的上下文環境中),就好像在接收器內部同樣:

    inline fun FragmentManager.inTransaction(func: FragmentTransaction.() -> Unit) {
        val fragmentTransaction = beginTransaction()
        fragmentTransaction.func()
        fragmentTransaction.commit()
    }
    //或:
    inline fun FragmentManager.inTransaction(func: FragmentTransaction.() -> FragmentTransaction) {
        beginTransaction().func().commit()
    }
    //這就是FragmentManager的擴展函數,接收一個Lambda表達式接收器做爲參數,FragmentTransaction做爲接收器
    
    //調用
    supportFragmentManager.inTransaction {
        //remove(fragmentA) 
        add(R.id.frameLayoutContent, fragmentB)
    }
    //須要說明的是在Lambda表達式中咱們調用FragmentTransaction的方法如add或者remove時並無使用修飾符,由於這是對FragmentTransaction的擴展函數.
    複製代碼

  • 函數類型

    一個函數要接受另外一個函數做爲參數,咱們得給它指定一個類型。好比上面的 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}
    //函數文本老是在大括號裏包裹着,在徹底語法中參數聲明是在括號內,類型註解是可選的,函數體是在&emsp;-> 以後,像下面這樣:
    val sum: (Int, Int) -> Int = {x, y -> x+y }
    //函數文本有時只有一個參數。若是 kotlin 能夠從它本生計算出簽名,那麼能夠省略這個惟一的參數,並會經過 it 隱式的聲明它
    ints.filter {it > 0}//這是 (it: Int) -> Boolean 的字面意思
    //注意若是一個函數接受另外一個函數作爲最後一個參數,該函數文本參數能夠在括號內的參數列表外的傳遞
    複製代碼
  • 函數表達式

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

    fun(x: Int, y: Int ): Int = x + y
    //函數表達式很像普通的函數聲明,除了省略了函數名。它的函數體能夠是一個表達式(像上面那樣)或者是一個塊:
    fun(x: Int, y: Int): Int {
        return x + y
    }
    //參數以及返回值和普通函數是同樣的,若是它們能夠從上下文推斷出參數類型,則參數類型能夠省略:
    ints.filter(fun(item) = item > 0)
    複製代碼
  • 閉包

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

    var sum = 0
    
    ints.filter{it > 0}.forEach {
        sum += it
    }
    print(sum)
    複製代碼
  • 函數表達式擴展

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

    val sum = fun Int.(other: Int): Int = this + other
    //接收類型必須在表達式函數中明確指定,但字面函數不用。字面函數能夠做爲擴展函數表達式,但只有接收類型能夠經過上下文推斷出來,表達式函數的擴展類型是一個帶接收者的函數:
    sum : Int.(other: Int) -> Int
    //使用
    1.sum(2)
    複製代碼

    字面函數(lambda)用->分隔函數體,函數表達式用=分隔函數體。

  • 內聯函數

    編譯器將使用函數的定義體來替代函數調用語句,這種替代行爲發生在編譯階段而非程序運行階段,也就是說把被調用的函數體複製到調用處,好處:

    • 減小了方法調用,壓棧,出棧的成本。
    • 在kotlin中,函數就是對象,當你調用某個函數的時候,就會建立相關的對象,內存的分配,虛擬調用都有開銷,內聯能夠減小成本。

    使用:

    inline fun <T> check(lock: Lock, body: () -> T): T {
            lock.lock()
            try {
                return body()
            } finally {
                lock.unlock()
            }
        }
    
    //---------調用----------------//
    fun run() {
         check(l, {"我是lambda方法體"})//l是一個Lock對象
    }
    //編譯器會把調用處換成這樣:
    fun run() {
          l.lock()
            try {
                return "我是lambda方法體"
            } finally {
                l.unlock()
            }
    }
    //如一個函數是inline的,那麼參數裏的函數,lambda也默認爲inline的。若是要部分參數爲非inline,可使用noinline關鍵字:
    inline fun doSomething(a:Int,b:Int,noinline doOther:(a:Int,b:Int)->Int){
        //...
    }
    複製代碼
  • 非局部返回

    Kotlin在lambda中不能直接使用return,要使用return配合標籤,但若是內聯,則能夠在lambda中直接使用return,該return直接做用於調用者函數,也就是說直接做用在調用的地方,誰調用退出誰。其餘內聯函數中的return同樣(return也被複制到了調用內聯函數的函數體裏)。若是要只退出lambda,可使用return@xxx。

  • 內聯屬性

    對屬性來講,咱們會有get,set的方法來操做這個屬性。 get,set就是個函數,咱們能夠標識他們爲內聯函數:

    val foo: Foo
        inline get() = Foo()
    
    var bar: Bar
        get() = ...
        inline set(v) { ... }
    //
    inline var bar: Bar
        get() = ...
        set(v) { ... }
    複製代碼
  • 實例化參數類型

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

    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::class.java)
    //咱們想要的僅僅是給這個函數傳遞一個類型,若是即像下面這樣就很方便:
    myTree.findParentOfType<MyTreeNodeType>()
    //爲了達到這個目的,內聯函數支持具體化的類型參數申明 reified
    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() 。普通的函數(沒有標記爲內聯的)不能有實例化參數。

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

    inline fun methodsOf<reified T>() = javaClass<T>().getMethods()
    
    fun main(s: Array<String>) {
        println(methodsOf<String>().joinToString('\n'))
    }
    複製代碼

協程

。。。

空安全

  • Kotlin 類型系統致力於消滅空引用(NPE),在 Kotlin 類型系統中能夠爲空和不可爲空的引用是不一樣的,屬性默認是要賦初值的,不能爲空:

    var a: String ="abc"
    a = null //編譯錯誤
    //容許爲空,咱們必須把它聲明爲可空的變量
    var b: String? = "abc"
    b = null
    //調用 a 的方法,而不用擔憂 NPE 異常:
    val l = a.length()
    //使用 b 調用一樣的方法就可能報錯
    val l = b.length() //錯誤:b 可能爲空
    複製代碼
  • 使用可空屬性(?)時的四種方式:

    • 在條件中檢查 null:

      val l = if (b != null) b.length() else -1
      //更復雜的條件:
      if (b != null && b.length() >0)
        print("Stirng of length ${b.length}")
      else
        print("Empty string")
      複製代碼
    • 安全調用,使用安全操做符,?.

      b?.length()
      //若是 b 不爲空則返回長度,不然返回空。這個表達式的的類型是 Int?,安全調用在鏈式調用是是頗有用的。好比,若是 Bob 是一個僱員可能分配部門(也可能不分配),若是咱們想獲取 Bob 的部門名做爲名字的前綴,就能夠這樣作:
      bob?.department?.head?.name
      //這樣的調用鏈在任何一個屬性爲空都會返回空
      複製代碼
    • Elvis 操做符,?:

      val l = b.length()?: -1
      //如if表達式:
      val l: Int = if (b != null) b.length() else -1
      
      //若是 ?: 左邊表達式不爲空則返回,不然返回右邊的表達式。注意右邊的錶帶式只有在左邊表達式爲空是纔會執行
      //注意在 Kotlin 中 throw return 是表達式,因此它們也能夠在 Elvis 操做符右邊。這是很是有用的,好比檢查函數參數是否爲空:
      fun foo(node: Node): String? {
        val parent = node.getParent() ?: return null
        val name = node.getName() ?: throw IllegalArgumentException("name expected")
      
        //...
      }
      複製代碼
    • !! 操做符

      NPE-lovers,咱們能夠用 b!! ,這會返回一個非空的 b 或者拋出一個 b 爲空的 NPE:

      val l = b !!.length()
      複製代碼
  • 安全轉換

    普通的轉換可能產生 ClassCastException 異常。另外一個選擇就是使用安全轉換,若是不成功就返回空:

    val aInt: Int? = a as? Int
    複製代碼

等式

  • 在 kotlin 中有兩種相等

    • 參照相等:參照相等是經過 === 操做符判斷的(不等是!== ) a===b 只有 a b 指向同一個對象是判別才成立。另外,你可使用內聯函數 identityEquals() 判斷參照相等:

      a.identityEquals(b)
      a identityEquals b
      複製代碼
    • 結構相等:結構相等是經過 == 判斷的。像 a == b 將會翻譯成:

      a?.equals(b) ?: b === null
      //若是 a 不是 null 則調用 equals(Any?) 函數,不然檢查 b 是否參照等於 null
      //注意徹底沒有必要爲優化你的代碼而將 a == null 寫成 a === null 編譯器會自動幫你作的
      複製代碼

      kotlin中==至關於java的equals,===至關於java的==

多重申明(解構申明)

var (name, age) = person
複製代碼
  • 意思就是一次性申明多個變量,並把=號右邊的對象的屬性拆箱出來賦值給變量。如:

    data class Person(var name: String, var age: Int) {
    }
    
    var person: Person = Person("Jone", 20)
    var (name, age) = person
    
    println("name: $name, age: $age")// 打印:name: Jone, age: 20
    複製代碼

    若是拆箱出對象的屬性:

    val name = person.component1()
    val age = person.component2()
    複製代碼

    person.component1,component2怎麼來的呢,Kotlin的數據類編譯器會根據主構造器中聲明的屬性, 自動推斷生成componentN() 函數羣, 這些函數與類的屬性對應, 函數名中的數字1到N,與屬性的聲明順序一致。那麼若是不是數據類就要本身編寫對象的componentN函數:

    class Person(val name: String, val age: Int) {
        operator fun component1(): String {
            return name
        }
    
        operator fun component2(): Int {
            return age
        }
    }
    複製代碼
  • 解構申明能夠用在for循環中:

    var personA: Person = Person("Door", 22, "ShanDong")
    var personB: Person = Person("Green", 30, "BeiJing")
    var personC: Person = Person("Dark", 23, "YunNan")
    var personD: Person = Person("Tool", 26, "GuanDong")
    var personE: Person = Person("Mark", 24, "TianJin")
    var pers = listOf(personA, personB, personC, personD, personE)
    for ((name, age) in pers) {
        println("name: $name, age: $age")
    }
    複製代碼
  • Map使用結構申明,Kotlin的標準庫中,對Map實現了這些擴展函數:

    operator fun <K, V> Map<K, V>.iterator(): Iterator<Map.Entry<K, V>> = entrySet().iterator()
    operator fun <K, V> Map.Entry<K, V>.component1() = getKey()
    operator fun <K, V> Map.Entry<K, V>.component2() = getValue()
    複製代碼

    因此在使用Map.Entry.getkey時使用調用到component1(),getvalue時調用component2():

    var personA: Person = Person("Door", 22, "ShanDong")
     var personB: Person = Person("Green", 30, "BeiJing")
     var personC: Person = Person("Dark", 23, "YunNan")
     var personD: Person = Person("Tool", 26, "GuanDong")
     var personE: Person = Person("Mark", 24, "TianJin")
    
     var map = HashMap<String, Person>()
     map.put("1", personA)
     map.put("2", personB)
     map.put("3", personC)
     map.put("4", personD)
     map.put("5", personE)
     for ((key, value) in map) {
         println("key: $key, value: $value")
     }
    複製代碼

// Log打印 key: 1, value: Person(name='Door', age=22, addr='ShanDong', mobile=null) key: 2, value: Person(name='Green', age=30, addr='BeiJing', mobile=null) key: 3, value: Person(name='Dark', age=23, addr='YunNan', mobile=null) key: 4, value: Person(name='Tool', age=26, addr='GuanDong', mobile=null) key: 5, value: Person(name='Mark', age=24, addr='TianJin', mobile=null)

## Ranges

- 表示從多少到多少,可用於if判斷和for循環,與in關鍵字配合,常見用法:

```kotlin
// Checking if value of comparable is in range. Optimized for number primitives.
if (i in 1..10) println(i)

if (x !in 1.0..3.0) println(x)

if (str in "island".."isle") println(str)

// Iterating over arithmetical progression of numbers. Optimized for number primitives (as indexed for-loop in Java).
for (i in 1..4) print(i) // prints "1234"

for (i in 4..1) print(i) // prints nothing

for (i in 4 downTo 1) print(i) // prints "4321"

for (i in 1..4 step 2) print(i) // prints "13"

for (i in (1..4).reversed()) print(i) // prints "4321"

for (i in (1..4).reversed() step 2) print(i) // prints "42"

for (i in 4 downTo 1 step 2) print(i) // prints "42"

for (x in 1.0..2.0) print("$x ") // prints "1.0 2.0 "

for (x in 1.0..2.0 step 0.3) print("$x ") // prints "1.0 1.3 1.6 1.9 "

for (x in 2.0 downTo 1.0 step 0.3) print("$x ") // prints "2.0 1.7 1.4 1.1 "

for (str in "island".."isle") println(str) // error: string range cannot be iterated over
複製代碼

原理參見標準庫中接口:Range ,Progressiont和和操做函數的擴展。

  • for in :

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

    for (i in array.indices)
        print(array[i])
    //-------------------------//
    for ((index, value) in array.withIndex()) {
        println("the element at $index is $value")
    }
    複製代碼

類型檢查和轉換

  • 類型檢查:is !is 表達式:

    //運行時檢查一個對象是不是某個特定類:
    if (obj is String) {
        print(obj.length)
    }
    
    if (obj !is String) { // same as !(obj is String)
        print("Not a String")
    }
    else {
        print(obj.length)
    }
    //智能轉換,編譯器會跟蹤 is 檢查靜態變量,並在須要的時候自動插入安全轉換:
    fun demo(x: Any) {
        if (x is String) {
            print(x.length) // x is automatically cast to String
        }
    }
    if (x !is String) return
    print(x.length) //x 自動轉換爲 String
    //在 || && 操做符,when 表達式和 whie 循環中:
     // x is automatically cast to string on the right-hand side of `||`
      if (x !is String || x.length == 0) return
    
      // x is automatically cast to string on the right-hand side of `&&`
      if (x is String && x.length > 0)
          print(x.length) // x is automatically cast to String
    
    when (x) {
        is Int -> print(x + 1)
        is String -> print(x.length + 1)
        is Array<Int> -> print(x.sum())
    }
    複製代碼
  • 轉換:

    用as 操做符來轉換類型:

    val x: String = y as String
    //null 不能被轉換爲 String 由於String不是 nullable,也就是說若是 y 是空的,則上面的代碼會拋出空異常。爲了 java 的轉換語句匹配咱們得像下面這樣:
    val x: String?= y as String?
    複製代碼

    "安全"轉換:

    val x: String ?= y as? String
    //爲了不拋出異常,能夠用 as? 這個安全轉換符,這樣失敗就會返回 null
    複製代碼

This表達式

  • 若是 this 沒有應用者,則指向的是最內層的閉合範圍。爲了在其它範圍中返回 this ,須要使用標籤:

    //this@lable
    class A { // implicit label @A
      inner class B { // implicit label @B
        fun Int.foo() { // implicit label @foo
          val a = this@A // A's this
          val b = this@B // B's this
    
          val c = this // foo()'s receiver, an Int
          val c1 = this@foo // foo()'s receiver, an Int
    
          val funLit = @lambda {String.() ->
            val d = this // funLit's receiver
            val d1 = this@lambda // funLit's receiver
          }
    
          val funLit2 = { (s: String) ->
            // foo()'s receiver, since enclosing function literal 
            // doesn't have any receiver
            val d1 = this 
          }
        }
      }
    }
    複製代碼

運算符號重載

。。。

operator fun get(position: Int) = dailyForecast[position]

//xxx[position]

一些使用時的筆記(建議)

  • 當須要把一個對象轉成另外一個,或有多個當前類或對象的.調用等,可使用這些擴展和函數,提升效率:

    • let
    • apply
    • run
    • with,with是個函數
  • 使用.isNullOrEmpty(),isNullOrBlank(),isBlank(),isEmpty(),isNotBlank(),isNotEmpty()來代替TextUtils判斷字符串。

  • 善用集合中的各類擴展函數,如reduce,filter,map,any,all,count,max,sumBy等等。

  • 構造函數裏的變量若是要在類中使用(類屬性)要標記定義關鍵字var或val,不然做用域不會是整個類,就像只是函數的參數同樣。

  • 一些高階函數,lambda {}裏不要用return,是返回的最後一行,若是用return他又是inline的話會返回了外層的函數。

  • 利用默認參數減小(java)方法重載

  • 可空也是一種類型(可空的xx類型),可接受實參爲空或具體類型的實例,可空類型的實例變量要解包(!!)後纔可使用原類型的屬性、方法。

  • for( index in 5..1),其中5和1只能是數值,若是用變量要用(x-1)包起來並轉成數字:for (i in (x+1)..(y+1))。

  • kotlin 沒有Volatile等併發編程的關鍵字,這是kotlin有意爲之,kotlin讓爲這應該讓函數庫來作,但並非不能用,可使用@Volatile,@Synchronized註解來使用相應功能,@Volatile標記jvm的備用字段爲volatile。wait(), notify()等Object(在Kotlin的Any中沒有這些方法)的方法能夠這麼使用:

    private val lock = java.lang.Object()
    
    fun produce() = synchronized(lock) {  
      while (items >= maxItems) {
        lock.wait()
      }
      Thread.sleep(rand.nextInt(100).toLong())
      items++
      println("Produced, count is $items: ${Thread.currentThread()}")
      lock.notifyAll()
    }
    
    fun consume() = synchronized(lock) {  
      while (items <= 0) {
        lock.wait()
      }
      Thread.sleep(rand.nextInt(100).toLong())
      items--
      println("Consumed, count is $items: ${Thread.currentThread()}")
      lock.notifyAll()
    }
    複製代碼
  • 當碰到用java時經常使用的若是不如爲就…時能夠用let等擴展:

    if (data != null) {
       nameTv.setText(data.name);
    }
    //kotlin
    data?.apply {
        nameTv.text=name
        info{"xxx"}
    }
    data?.let{
        nameTv.text=it.name
    }
    mOnActionListener?.onAction()
    複製代碼
  • 使用高階函數+函數對象定義監聽器(java中的onClickListener等)

  • 當可變屬性(var)定義爲可空(?)時編譯器報錯:Smart cast to 'Type' is impossible, because 'variable' is a mutable property that could have been changed by this time 解決的幾種辦法:

    var name: String? = null
    val names: ArrayList<String> = ArrayList()
    //1:若是能用只讀,改爲val。
    //2::用一個本地變量接收再使用:
    fun foo() {
      val nameLoc = a.name
      if(nameLoc != null) {
         names.add(name);
      }
    }
    //3:用?操做符
    name?.let{
         names.add(name);
    }
    //4:若是是在用Elvis操做符
    foo1(name?:"")
    //循環中
     names.add(name?:continue);
    複製代碼

做者:竹塵居士

博客:http://zhuchen.vip/2018/04/01/kotlin/kotlin-learn-summary.html

相關文章
相關標籤/搜索