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

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

對象相關

  • 對象表達式:相當于Java匿名類部類皮胡,在使用的地方被立即執(zhí)行:

    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 中我們可以方便的通過對象聲明來獲得一個單例,對象聲明是延遲加載的血当, 在第一次使用的時候被初始化幻赚,對象聲明不是一個表達式,不能用在賦值語句的右邊臊旭,對象聲明不能在局部作用域(即直接嵌套在函數(shù)內部)落恼,但是它們可以嵌套到其他對象聲明或非內部類中,

    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 // 正確
    }
    
  • 伴隨(生)對象:相當于靜態(tài)內部類+該類的靜態(tài)屬性喊括,所在的類被加載,伴生對象被初始化(和 Java 的靜態(tài)初始是對應):

    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 {
    
        }
    }
    //盡管伴隨對象的成員很像其它語言中的靜態(tài)成員郑什,但在運行時它們任然是真正類的成員實例,比如可以實現(xiàn)接口:
    interface Factory<T> {
        fun create(): T
    }
    
    class MyClass {
        companion object : Factory<MyClass> {
            override fun create(): MyClass = MyClass()
        }
    }
    //如果你在 JVM 上使用 @JvmStatic 注解蒲肋,你可以有多個伴隨對象生成為真實的靜態(tài)方法和屬性
    

屬性字段相關

  • 備用字段:Kotlin中不能有field蘑拯,但在自定義getter/setter的時候需要直接訪問屬性而不是又通過getter/settter來取值賦值(循環(huán)調用)。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() // 參數(shù)類型是自動推導
            }
            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的屬性初始化前訪問他,會導致一個特定異常,告訴你訪問的時候值還沒有初始化
    

復寫屬性

  • 屬性可復寫碑诉,在主構造函數(shù)中就可使用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()方法,是將其托付給一個代理類泪幌,從而每個使用類中的該屬性可以通過代理類統(tǒng)一管理盲厌,再也不用在每個類中,對其聲明重復的操作方法祸泪。語法:

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

    使用場景:

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

    Kotlin標準庫中已實現(xiàn)的代理:

    • 延遲加載(Lazy):lazy()是一個函數(shù), 接受一個Lambda表達式作為參數(shù), 返回一個Lazy類型的實例,這個實例可以作為一個委托, 實現(xiàn)延遲加載屬性(lazy property): 第一次調用 get() 時, 將會執(zhí)行 lazy() 函數(shù)受到的Lambda 表達式,然后會記住這次執(zhí)行的結果, 以后所有對 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的三個參數(shù)為:
        • SYNCHRONIZED:鎖定,用于確保只有一個線程可以初始化[Lazy]實例。
        • PUBLICATION:初始化函數(shù)可以在并發(fā)訪問未初始化的[Lazy]實例值時調用幾次阀湿,赶熟,但只有第一個返回的值將被用作[Lazy]實例的值。
        • NONE:沒有鎖用于同步對[Lazy]實例值的訪問; 如果從多個線程訪問實例陷嘴,是線程不安全的映砖。此模式應僅在高性能至關重要,并且[Lazy]實例被保證永遠不會從多個線程初始化時使用灾挨。
    • 可觀察屬性(Observable):Delegates.observable() 函數(shù)接受兩個參數(shù): 第一個是初始化值, 第二個是屬性值變化事件的響應器(handler).這種形式的委托邑退,采用了觀察者模式,其會檢測可觀察屬性的變化劳澄,當被觀察屬性的setter()方法被調用的時候地技,響應器(handler)都會被調用(在屬性賦值處理完成之后)并自動執(zhí)行執(zhí)行的lambda表達式,同時響應器會收到三個參數(shù):被賦值的屬性, 賦值前的舊屬性值, 以及賦值后的新屬性值秒拔。:

      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()函數(shù)接受兩個參數(shù): 第一個是初始化值, 第二個是屬性值變化事件的響應器(handler),是可觀察屬性(Observable)的一個特例,不同的是在響應器指定的自動執(zhí)行執(zhí)行的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:在實際開發(fā)時,我們可能會設置可為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實例作為參數(shù),將map實例本身作為屬性的委托娩践,屬性的名稱與map中的key是一致的活翩,也就是意味著我們可以很簡單的從一個動態(tài)地map中創(chuàng)建一個對象實例:

      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()。自定義委托必須要實現(xiàn):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表示持有該對象的對象,
      //第二個參數(shù) property 是該值的類型,
      //第三個參數(shù) value 就是屬性的值了
      

      ?

密封類

  • 密封類:類的擴展,但每個枚舉常量只存在一個實例,而密封類的一個子類可以有可包含狀態(tài)的多個實例辣辫,雖然密封類也可以有子類旦事,但是所有子類都必須在與密封類自身相同的文件中聲明,間接的子類不受限制急灭。密封類是自身抽象的姐浮,它不能直接實例化并可以有抽象(abstract)成員。密封類不允許有非-private 構造函數(shù)(其構造函數(shù)默認為 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類似可有抽象方法與實現(xiàn)方法蛋逾,不可保存狀態(tài),屬性必須是抽象的或唯一值的(無備用屬性)窗悯。

    ?

擴展

  • 不需要在類中去添加方法区匣,在外部就可以給任何地方的類添加我們想要的方法,替換掉java中的FileUtil,xxxUtil等如:

    Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list))
    //變成
    list.swap(list.binarySearch(otherList.max()), list.max())
    

    擴展是被靜態(tài)解析的:擴展實際上并沒有修改它所擴展的類,只是讓這個類的實例對象能夠通過"."調用新的函數(shù)蒋院。需要強調的是擴展函數(shù)是靜態(tài)分發(fā)的亏钩,舉個例子,它們并不是接受者類型的虛擬方法。這意味著擴展函數(shù)的調用是由發(fā)起函數(shù)調用的表達式的(對象)類型決定的悦污,而不是在運行時動態(tài)獲得的表達式的(對象)類型決定铸屉。比如:

    open class C 
    
    class D: C()
    
    fun C.foo() = "c" 
    
    fun D.foo() = "d"
    
    fun printFoo(c: C) { 
        println(c.foo())
    } 
    
    printFoo(D())
    //輸出 c,因為這里擴展函數(shù)的調用決定于聲明的參數(shù) c 的類型切端,也就是 C彻坛。
    
    

    如果有同名同參數(shù)的成員函數(shù)和擴展函數(shù),調用的時候會使用成員函數(shù),比如:

    class C {
        fun foo() { println("member") }
    
    }
    fun C.foo() { println("extension") }
    C().foo()
    //輸出"member",而不是"extension"
    //可以通過不同的函數(shù)簽名的方式重載函數(shù)的成員函數(shù):
    fun C.foo(i:Int) { println("extention") }
    C().foo(1)
    //輸出 “extentions”昌屉。
    

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

    package foo.bar
    fun Baz.goo() { ... }
    //為了在除聲明的包外使用這個擴展钙蒙,我們需要在別的文件中使用時導入:
    //-------------------------------------------------//
    package com.example.usage
    
    import foo.bar.goo // 導入所有名字叫 "goo" 的擴展
    
                        // 或者
    
    import foo.bar.* // 導入foo.bar包下得所有數(shù)據(jù)
    
    fun usage(baz: Baz) {
        baz.goo()
    }
    

    ?

  • 函數(shù)擴展:

    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()` 函數(shù)中 `this` 持有的值是 `l`
    
  • 可空的接收者:
    使用空接收者類型進行定義。這樣的擴展使得间驮,即使是一個空對象仍然可以調用該擴展躬厌,然后在擴展的內部進行 this == null 的判斷。這樣你就可以在 Kotlin 中任意調用 toString() 方法而不進行空指針檢查:空指針檢查延后到擴展函數(shù)中完成:

    fun Any?.toString(): String {
        if (this == null) return "null"
        // 在空檢查之后竞帽,`this` 被自動轉為非空類型扛施,因此 toString() 可以被解析到任何類的成員函數(shù)中
        return toString()
    }
    

    T.所以擴展是用.來使用的,如Kotlin的to函數(shù)就是A to B 以空格使用屹篓。

  • 屬性擴展

    注意疙渣,由于擴展并不會真正給類添加了成員屬性,因此也沒有辦法讓擴展屬性擁有一個備份字段.這也是為什么初始化函數(shù)不允許有擴展屬性堆巧。擴展屬性只能夠通過明確提供 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()
    

數(shù)據(jù)類

我們經常創(chuàng)建一個只保存數(shù)據(jù)的類妄荔。在這樣的類中一些函數(shù)只是機械的對它們持有的數(shù)據(jù)進行,如從服務端返回的Json字符串對象映射成Java類谍肤。data類使用:

data class 類名(var param1 :數(shù)據(jù)類型,...){}
data class 類名 可見性修飾符 constructor(var param1 : 數(shù)據(jù)類型 = 默認值,...)
//data為聲明數(shù)據(jù)類的關鍵字啦租,必須書寫在class關鍵字之前。
//在沒有結構體的時候荒揣,大括號{}可省略篷角。
//構造函數(shù)中必須存在至少一個參數(shù),并且必須使用val或var修飾系任。這一點在下面數(shù)據(jù)類特性中會詳細講解内地。
//參數(shù)的默認值可有可無。(若要實例一個無參數(shù)的數(shù)據(jù)類赋除,則就要用到默認值)
// 定義一個名為Person的數(shù)據(jù)類:
data class Preson(var name : String,val sex : Int, var age : Int)

data類必須滿足的條件:

  • 主構造函數(shù)需要至少有一個參數(shù)
  • 主構造函數(shù)的所有參數(shù)需要標記為 val 或 var;
  • 數(shù)據(jù)類不能是抽象非凌、開放举农、密封或者內部的;
  • 數(shù)據(jù)類是可以實現(xiàn)接口的敞嗡,如(序列化接口)颁糟,同時也是可以繼承其他類的,如繼承自一個密封類喉悴。

約定俗成的規(guī)定:當構造函數(shù)中的參過多時棱貌,為了代碼的閱讀性,一個參數(shù)的定義占據(jù)一行箕肃。

編輯器為我們做的事情:

  • 生成equals()函數(shù)與hasCode()函數(shù)
  • 生成toString()函數(shù)婚脱,由類名(參數(shù)1 = 值1,參數(shù)2 = 值2,....)構成
  • 由所定義的屬性自動生成component1()障贸、component2()错森、...、componentN()函數(shù)篮洁,其對應于屬性的聲明順序涩维。
  • copy()函數(shù)。修改部分屬性袁波,但是保持其他不變瓦阐。

copy函數(shù)的使用:

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)

?

泛型

  • 兩種型變:

    • 協(xié)變:當A ≦ B時,如果有f(A) ≦ f(B),那么f叫做協(xié)變篷牌;

    • 逆變:當A ≦ B時,如果有f(B) ≦ f(A),那么f叫做逆變睡蟋;

      其余為不變。

    協(xié)變娃磺,逆變薄湿,不變來原于子類可以安全的向上轉型為父類。

  • 不變(java的泛型<T>是不變的):

    ArrayList<Number> list = new ArrayList<Integer>(); //type mismatch  
    
  • 協(xié)變:

    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<Integer>可以添加Interger及其子類,List<Float>可以添加Float及其子類敛惊,List<Integer>渊鞋、List<Float>都是List<? extends Animal>的子類型,如果能將Float的子類添加到List<? extends Animal>中瞧挤,就說明Float的子類也是可以添加到List<Integer>中的锡宋,顯然是不可行。故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):協(xié)變只能取(生產者)呢簸,逆變只能寫(消費者),如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修飾符使協(xié)變與逆變使用起來更方便,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既要返回又要在參數(shù)中消費:

    class Array<T>(val size: Int) {
        fun get(index: Int): T { /* ... */ }
        fun set(index: Int, value: T) { /* ... */ }
    }
    //這個類既不能是協(xié)變的也不能是逆變的替裆,這會在一定程度上降低靈活性【轿剩考慮下面的函數(shù)形式:
    fun copy(from: Array<out Any>, to: Array<Any>) {
     // ...
    }
    
  • 星投影

    有時你對類型參數(shù)一無所知辆童,但任然想安全的使用它。保險的方法就是定一個該范型的投影惠赫,每個該范型的正確實例都將是該投影的子類把鉴,F(xiàn)oo<*>

  • 范型約束

    上界:最常用的類型約束是上界,在 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的靜態(tài)內部類相似:

    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
    
  • 匿名內部類使套,通過對象表達式創(chuàng)建:

    window.addMouseListener(object: MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
            // ...
        }
    
        override fun mouseEntered(e: MouseEvent) {
            // ...
        }
    })
    //如果對象是函數(shù)式的 java 接口的實例(比如只有一個抽象方法的 java 接口),你可以用一個帶接口類型的 lambda 表達式創(chuàng)建它
    val listener = ActionListener { println("clicked") }
    

    ?

函數(shù)

  • 函數(shù)參數(shù)

    • 標準參數(shù):

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

    
    - 默認參數(shù),函數(shù)參數(shù)可以設置默認值,當調用函數(shù)時參數(shù)被忽略會使用默認值鞠柄。這樣相比其他語言可以減少重載:
    
    ```kotlin
    fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size ) {
    ...
    }
    
    • 命名參數(shù):在調用函數(shù)時可以用參數(shù)的命名來賦值參數(shù)侦高。這對于那種有大量參數(shù)的函數(shù)很方便:

      fun reformat(str: String, normalizeCase: Boolean = true,upperCaseFirstLetter: Boolean = true,
                   divideByCamelHumps: Boolean = false,
                   wordSeparator: Char = ' ') {
      ...
      }
      //調用:
      //使用默認參數(shù):
      reformat(str)
      //調用非默認參數(shù):
      reformat(str, true, true, false, '_')
      //使用命名參數(shù):
      reformat(str,
          normalizeCase = true,
          uppercaseFirstLetter = true,
          divideByCamelHumps = false,
          wordSeparator = '_'
        )
      //不需要全部參數(shù):
      reformat(str, wordSeparator = '_')
      //注意,命名參數(shù)語法不能夠被用于調用Java函數(shù)中,因為Java的字節(jié)碼不能確保方法參數(shù)命名的不變性
      

      默認參數(shù)可能只是給參數(shù)一個默認值,而命名參數(shù)則給參數(shù)一個有意義的名字厌杜。

    • 不帶返回值的參數(shù):

      如果函數(shù)不會返回任何有用值奉呛,那么他的返回類型就是 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?) {
          ...
      }
      
      
    • 變長參數(shù):

      函數(shù)的參數(shù)(通常是最后一個參數(shù))可以用 vararg 修飾符進行標記:

      fun <T> asList(vararg ts: T): List<T> {
          val result = ArrayList<T>()
          for (t in ts)
              result.add(t)
          return result
      }
      //標記后,允許給函數(shù)傳遞可變長度的參數(shù):
      val list = asList(1, 2, 3)
      //只有一個參數(shù)可以被標注為 vararg 计螺。加入vararg并不是列表中的最后一個參數(shù),那么后面的參數(shù)需要通過命名參數(shù)語法進行傳值,再或者如果這個參數(shù)是函數(shù)類型,就需要通過lambda法則.
      
      //當調用變長參數(shù)的函數(shù)時,我們可以一個一個的傳遞參數(shù)瞧壮,比如 asList(1, 2, 3)登馒,或者我們要傳遞一個 array 的內容給函數(shù),我們就可以使用 * 前綴操作符:
      val a = array(1, 2, 3)
      val list = asList(-1, 0, *a, 4)
      

      各種類型參數(shù)定義與使用與Python相似

  • 單表達式函數(shù):

    //當函數(shù)只返回單個表達式時咆槽,大括號可以省略并在 = 后面定義函數(shù)體:
    fun double(x: Int): Int = x*2
    //在編譯器可以推斷出返回值類型的時候,返回值的類型可以省略:
    fun double(x: Int) = x * 2
    

    ?

  • 函數(shù)范圍

    Kotlin 中可以在文件頂級聲明函數(shù)陈轿,這就意味者你不用像在Java,C#或是Scala一樣創(chuàng)建一個類來持有函數(shù)。除了頂級函數(shù)秦忿,Kotlin 函數(shù)可以聲明為局部的麦射,作為成員函數(shù)或擴展函數(shù):

    //局部函數(shù)
    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())
    }
    //局部函數(shù)可以訪問外部函數(shù)的局部變量(比如閉包)
    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])
    }
    //局部函數(shù)甚至可以返回到外部函數(shù)
    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
    }
    
  • 成員函數(shù)

    跟java一樣,類中的成員灯谣。

  • 泛型函數(shù)

    跟java一樣潜秋。

  • 尾遞歸函數(shù)

    Kotlin 支持函數(shù)式編程的尾遞歸。這個允許一些算法可以通過循環(huán)而不是遞歸解決問題胎许,從而避免了棧溢出峻呛。當函數(shù)被標記為 tailrec 時,編譯器會優(yōu)化遞歸辜窑,并用高效迅速的循環(huán)代替它:

    tailrec fun findFixPoint(x: Double = 1.0): Double 
        = if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))
    //使用 tailrec 修飾符必須在最后一個操作中調用自己钩述。在遞歸調用代碼后面是不允許有其它代碼的,并且也不可以在 try/catch/finall 塊中進行使用谬擦。當前的尾遞歸只在 JVM 的后端中可以用
    
  • 高階函數(shù)

    高階函數(shù)就是可以接受函數(shù)作為參數(shù)或者返回一個函數(shù)的函數(shù):

    fun <T> lock(lock: Lock, body: () -> T ) : T {
        lock.lock()
        try {
            return body()
        }
        finally {
            lock.unlock()
        }
    }
    //body 是一個類型為 () -> T 的函數(shù)切距,可以這樣使用
    fun toBeSynchroized() = sharedResource.operation()
    val result = lock(lock, ::toBeSynchroized)
    //更方便的是傳一個字面函數(shù)(lambda表達式)
    val result = lock(lock, {
    sharedResource.operation() })
    //在 kotlin 中有一個約定,如果某一個函數(shù)的最后一個參數(shù)是函數(shù)惨远,并且你向那個位置傳遞了一個 lambda 表達式谜悟,那么,你可以在括號外面定義這個 lambda 表達式:
    lock (lock) {
        sharedResource.operation()
    }
    //高階函數(shù)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}
    //如果字面函數(shù)只有一個參數(shù)北秽,則聲明可以省略葡幸,名字就是 it :
    ints.map {it * 2}
    //這樣就可以寫LINQ-風格的代碼了:
    strings.filter{ it.length == 5 }.sortedBy{ it }.map{ it.toUpperCase() }
    
    
  • 字面函數(shù)和函數(shù)表達式(lambda表達式),字面函數(shù)或函數(shù)表達式就是一個 "匿名函數(shù)",也就是沒有聲明的函數(shù)贺氓,但立即作為表達式傳遞下去:

    max(strings, {a, b -> a.length < b.length })
    //max 函數(shù)就是一個高階函數(shù),它接受函數(shù)作為第二個參數(shù)蔚叨。第二個參數(shù)是一個表達式所以本生就是一個函數(shù),即字面函數(shù)辙培。作為一個函數(shù)蔑水,相當于:
    fun compare(a: String, b: String) : Boolean = a.length < b.length
    

    如果只有一個參數(shù)lambda中可以不寫參數(shù)變量,直接用it表示參數(shù)扬蕊,如:

    {it.length}
    
  • Lambda表達式接收器:

    (函數(shù)字面量接收器,在定義高階函數(shù)參數(shù)時使用)是上面兩者的結合——一個以指定接收器的擴展函數(shù)為參數(shù)的高階函數(shù)搀别。所以在我們傳遞的Lambda表達式中我們可以直接訪問接收器的公共方法和屬性(在接受器的上下文環(huán)境中),就好像在接收器內部一樣:

    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的擴展函數(shù),接收一個Lambda表達式接收器作為參數(shù),FragmentTransaction作為接收器
    
    //調用
    supportFragmentManager.inTransaction {
        //remove(fragmentA)    
        add(R.id.frameLayoutContent, fragmentB)
    }
    //需要說明的是在Lambda表達式中我們調用FragmentTransaction的方法如add或者remove時并沒有使用修飾符,因為這是對FragmentTransaction的擴展函數(shù).
    

    ?

  • 函數(shù)類型

    一個函數(shù)要接受另一個函數(shù)作為參數(shù)尾抑,我們得給它指定一個類型歇父。比如上面的 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
    }
    //參數(shù) less 是 (T, T) -> Boolean類型蒂培,也就是接受倆個 T 類型參數(shù)返回一個 Boolean:如果第一個參數(shù)小于第二個則返回真。在函數(shù)體第四行榜苫, less 是用作函數(shù)护戳。
    //一個函數(shù)類型可以像上面那樣寫,也可有命名參數(shù)
    val compare: (x: T,y: T) -> Int = ...
    
  • 函數(shù)文本語法

    函數(shù)文本的完全寫法:

    val sum = {x: Int,y: Int -> x + y}
    //函數(shù)文本總是在大括號里包裹著垂睬,在完全語法中參數(shù)聲明是在括號內媳荒,類型注解是可選的,函數(shù)體是在 -> 之后羔飞,像下面這樣:
    val sum: (Int, Int) -> Int = {x, y -> x+y }
    //函數(shù)文本有時只有一個參數(shù)肺樟。如果 kotlin 可以從它本生計算出簽名,那么可以省略這個唯一的參數(shù)逻淌,并會通過 it 隱式的聲明它
    ints.filter {it > 0}//這是 (it: Int) -> Boolean  的字面意思
    //注意如果一個函數(shù)接受另一個函數(shù)做為最后一個參數(shù)么伯,該函數(shù)文本參數(shù)可以在括號內的參數(shù)列表外的傳遞
    
  • 函數(shù)表達式

    指定返回值的函數(shù)在大多數(shù)情形中是不必要的,因為返回值是可以自動推斷的卡儒。然而田柔,如果你需要自己指定,可以用函數(shù)表達式來做:

    fun(x: Int, y: Int ): Int = x + y
    //函數(shù)表達式很像普通的函數(shù)聲明骨望,除了省略了函數(shù)名硬爆。它的函數(shù)體可以是一個表達式(像上面那樣)或者是一個塊:
    fun(x: Int, y: Int): Int {
        return x + y
    }
    //參數(shù)以及返回值和普通函數(shù)是一樣的,如果它們可以從上下文推斷出參數(shù)類型擎鸠,則參數(shù)類型可以省略:
    ints.filter(fun(item) = item > 0)
    
  • 閉包

    一個字面函數(shù)或者表達式函數(shù)可以訪問閉包缀磕,即訪問自身范圍外的聲明的變量。不像 java 那樣在閉包中的變量是被捕獲修改的:

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

    表達式函數(shù)的擴展和普通的擴展區(qū)別是它有接收類型的規(guī)范:

    val sum = fun Int.(other: Int): Int = this + other
    //接收類型必須在表達式函數(shù)中明確指定劣光,但字面函數(shù)不用袜蚕。字面函數(shù)可以作為擴展函數(shù)表達式,但只有接收類型可以通過上下文推斷出來,表達式函數(shù)的擴展類型是一個帶接收者的函數(shù):
    sum : Int.(other: Int) -> Int
    //使用
    1.sum(2)
    

    字面函數(shù)(lambda)用->分隔函數(shù)體绢涡,函數(shù)表達式用=分隔函數(shù)體牲剃。

  • 內聯(lián)函數(shù)

    編譯器將使用函數(shù)的定義體來替代函數(shù)調用語句,這種替代行為發(fā)生在編譯階段而非程序運行階段雄可,也就是說把被調用的函數(shù)體復制到調用處凿傅,好處:

    • 減少了方法調用,壓棧数苫,出棧的成本聪舒。
    • 在kotlin中,函數(shù)就是對象虐急,當你調用某個函數(shù)的時候过椎,就會創(chuàng)建相關的對象,內存的分配戏仓,虛擬調用都有開銷疚宇,內聯(lián)可以減少成本。

    使用:

    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()
            }
    }
    //如一個函數(shù)是inline的赏殃,那么參數(shù)里的函數(shù)敷待,lambda也默認為inline的。如果要部分參數(shù)為非inline仁热,可以使用noinline關鍵字:
    inline fun doSomething(a:Int,b:Int,noinline doOther:(a:Int,b:Int)->Int){
        //...
    }
    
  • 非局部返回

    Kotlin在lambda中不能直接使用return榜揖,要使用return配合標簽,但如果內聯(lián)抗蠢,則可以在lambda中直接使用return举哟,該return直接作用于調用者函數(shù),也就是說直接作用在調用的地方迅矛,誰調用退出誰妨猩。其他內聯(lián)函數(shù)中的return一樣(return也被復制到了調用內聯(lián)函數(shù)的函數(shù)體里)。如果要只退出lambda秽褒,可以使用return@xxx壶硅。

  • 內聯(lián)屬性

    對屬性來說,我們會有get销斟,set的方法來操作這個屬性庐椒。
    get,set就是個函數(shù),我們可以標識他們?yōu)閮嚷?lián)函數(shù):

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

    有時我們需要訪問傳遞過來的類型蚂踊,把它作為參數(shù):

    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)
    //我們想要的僅僅是給這個函數(shù)傳遞一個類型约谈,如果即像下面這樣就很方便:
    myTree.findParentOfType<MyTreeNodeType>()
    //為了達到這個目的,內聯(lián)函數(shù)支持具體化的類型參數(shù)申明 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 修飾符檢查類型參數(shù)犁钟,既然它可以在函數(shù)內部訪問了棱诱,也就基本上接近普通函數(shù)了。因為函數(shù)是內聯(lián)的特纤,所以不許要反射军俊,像 !is `as`這樣的操作都可以使用。同時捧存,我們也可以像上面那樣調用它了 myTree.findParentOfType<MyTreeNodeType>() 粪躬。普通的函數(shù)(沒有標記為內聯(lián)的)不能有實例化參數(shù)。

    在很多情況下會使用反射訪問類型數(shù)據(jù)昔穴,我們仍然可以使用實例化的類型參數(shù) javaClass() 來訪問它:

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

    ?

協(xié)程

镰官。。吗货。

空安全

  • Kotlin 類型系統(tǒng)致力于消滅空引用(NPE)泳唠,在 Kotlin 類型系統(tǒng)中可以為空和不可為空的引用是不同的,屬性默認是要賦初值的宙搬,不能為空:

    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
      
      //如果 ?: 左邊表達式不為空則返回脚仔,否則返回右邊的表達式。注意右邊的表帶式只有在左邊表達式為空是才會執(zhí)行
      //注意在 Kotlin 中 throw return 是表達式舆绎,所以它們也可以在 Elvis 操作符右邊鲤脏。這是非常有用的,比如檢查函數(shù)參數(shù)是否為空:
      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 指向同一個對象是判別才成立姑食。另外,你可以使用內聯(lián)函數(shù) identityEquals() 判斷參照相等:

      a.identityEquals(b)
      a identityEquals b
      
    • 結構相等:結構相等是通過 == 判斷的茅坛。像 a == b 將會翻譯成:

      a?.equals(b) ?: b === null
      //如果 a 不是 null 則調用 equals(Any?) 函數(shù)音半,否則檢查 b 是否參照等于 null
      //注意完全沒有必要為優(yōu)化你的代碼而將 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的數(shù)據(jù)類編譯器會根據(jù)主構造器中聲明的屬性, 自動推斷生成componentN() 函數(shù)群, 這些函數(shù)與類的屬性對應, 函數(shù)名中的數(shù)字1到N,與屬性的聲明順序一致。那么如果不是數(shù)據(jù)類就要自己編寫對象的componentN函數(shù):

    class Person(val name: String, val age: Int) {
        operator fun component1(): String {
            return name
        }
    
        operator fun component2(): Int {
            return age
        }
    }
    
  • 解構申明可以用在for循環(huán)中:

    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實現(xiàn)了這些擴展函數(shù):

    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循環(huán)肆饶,與in關鍵字配合,常見用法:

    // 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和和操作函數(shù)的擴展驯镊。

  • 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 檢查靜態(tài)變量,并在需要的時候自動插入安全轉換:
    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 循環(huán)中:
     // 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]

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

  • 當需要把一個對象轉成另一個绒窑,或有多個當前類或對象的.調用等,可以使用這些擴展和函數(shù)舔亭,提高效率:

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

  • 善用集合中的各種擴展函數(shù)钦铺,如reduce,filter,map,any,all,count,max,sumBy等等订雾。

  • 構造函數(shù)里的變量如果要在類中使用(類屬性)要標記定義關鍵字var或val,否則作用域不會是整個類矛洞,就像只是函數(shù)的參數(shù)一樣洼哎。

  • 一些高階函數(shù),lambda {}里不要用return沼本,是返回的最后一行噩峦,如果用return他又是inline的話會返回了外層的函數(shù)。

  • 利用默認參數(shù)減少(java)方法重載

  • 可空也是一種類型(可空的xx類型)抽兆,可接受實參為空或具體類型的實例识补,可空類型的實例變量要解包(!!)后才可以使用原類型的屬性、方法辫红。

  • for( index in 5..1),其中5和1只能是數(shù)值凭涂,如果用變量要用(x-1)包起來并轉成數(shù)字:for (i in (x+1)..(y+1))。

  • kotlin 沒有Volatile等并發(fā)編程的關鍵字厉熟,這是kotlin有意為之导盅,kotlin讓為這應該讓函數(shù)庫來做,但并不是不能用揍瑟,可以使用@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()
    
  • 使用高階函數(shù)+函數(shù)對象定義監(jiān)聽器(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?:"")
    //循環(huán)中
     names.add(name?:continue);
    

作者:竹塵居士
博客:http://zhuchenju.com

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末巢株,一起剝皮案震驚了整個濱河市槐瑞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌阁苞,老刑警劉巖困檩,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異那槽,居然都是意外死亡悼沿,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門骚灸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來糟趾,“玉大人,你說我怎么就攤上這事甚牲∫逯#” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵丈钙,是天一觀的道長非驮。 經常有香客問我,道長著恩,這世上最難降的妖魔是什么院尔? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮喉誊,結果婚禮上邀摆,老公的妹妹穿的比我還像新娘。我一直安慰自己伍茄,他們只是感情好栋盹,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著敷矫,像睡著了一般例获。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上曹仗,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天榨汤,我揣著相機與錄音,去河邊找鬼怎茫。 笑死收壕,一個胖子當著我的面吹牛妓灌,可吹牛的內容都是我干的。 我是一名探鬼主播蜜宪,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼虫埂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了圃验?” 一聲冷哼從身側響起掉伏,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎澳窑,沒想到半個月后斧散,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡摊聋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年颅湘,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片栗精。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖瞻鹏,靈堂內的尸體忽然破棺而出悲立,到底是詐尸還是另有隱情,我是刑警寧澤新博,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布薪夕,位于F島的核電站,受9級特大地震影響赫悄,放射性物質發(fā)生泄漏原献。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一埂淮、第九天 我趴在偏房一處隱蔽的房頂上張望姑隅。 院中可真熱鬧,春花似錦倔撞、人聲如沸讲仰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鄙陡。三九已至,卻和暖如春躏啰,著一層夾襖步出監(jiān)牢的瞬間趁矾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工给僵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留毫捣,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像培漏,于是被迫代替她去往敵國和親溪厘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內容

  • Scala與Java的關系 Scala與Java的關系是非常緊密的E票畸悬! 因為Scala是基于Java虛擬機,也就是...
    燈火gg閱讀 3,421評論 1 24
  • SwiftDay011.MySwiftimport UIKitprintln("Hello Swift!")var...
    smile麗語閱讀 3,827評論 0 6
  • 1. Java基礎部分 基礎部分的順序:基本語法珊佣,類相關的語法蹋宦,內部類的語法,繼承相關的語法咒锻,異常的語法冷冗,線程的語...
    子非魚_t_閱讀 31,587評論 18 399
  • 昨天,朋友發(fā)了一封婚禮邀請函惑艇,我毫不思索的打開鏈接蒿辙,然后我發(fā)現(xiàn),我發(fā)現(xiàn)我被騙了...... 今早(其實也不早了)中...
    易水寒1213閱讀 266評論 0 0
  • 睡不著滨巴!思考30人生路…
    天志仔閱讀 139評論 0 0