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

竹塵居士發表於2018-04-01

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

物件相關

  • 物件表示式:相當於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 中我們可以方便的通過物件宣告來獲得一個單例,物件宣告是延遲載入的, 在第一次使用的時候被初始化,物件宣告不是一個表示式,不能用在賦值語句的右邊,物件宣告不能在區域性作用域(即直接巢狀在函式內部),但是它們可以巢狀到其他物件宣告或非內部類中,

    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 的靜態初始是對應):

    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
    複製程式碼
  • 備用屬性:功能與備用欄位類似。:

    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。:

    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 >。但是這裡不能向list001、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

相關文章