Kotlin Extension 使用要點

Extension 既擴展方法,若寫過動態(tài)語言就一定知道

class Foo{}

foo = new Foo()
foo.func = funtion (args){
  //do something
}

之后就可以調(diào)用foo.func()函數(shù)這就是動態(tài)擴展

上面的例子是給實例擴展當然動態(tài)語言給類擴展也是沒有問題的,而kotlin中可以對類進行擴展

以類成員函數(shù)的方式聲明

package Bar.Foo

class Father(){
    fun takeMoney(kiss:Kiss):Money{
      return SelfMoney.half
    }
  
    fun sleep(){}
}

class Mather(){
  //Father's extension
  fun Father().buyGucci():Gucci{
    val kiss = giveKiss()
    val money = takeMoney(kiss)
    //pay money
    sleep()//father.sleep()
    this@Mather.sleep()//mather.sleep() and this@Mather == Mather.this in java
    return gucci
  }
  
  fun wantHappy(){
      val husband = Father()
      husband.buyGucci()
  }
  
  fun giveKiss():Kiss{
      return kiss
  }
  
  fun sleep(){}
}

首先說明,kotlin中也有包的概念但是包下頂級元素不需要是類,kotlin中的第一公民是函數(shù)。

從上面的代碼中我想說明以下幾點擴展函數(shù)的特性

  • 擴展函數(shù)是對類的擴展,對于Father來說相當于在自己里面定義了buyGucci函數(shù)

  • 同時擴展函數(shù)中隱含著兩個引用,既有Mather的實例的引用(dispatch receiver

    )又有father實例的引用(extension receiver)所以可以直接調(diào)用兩個類的函數(shù)以及屬性

  • 在其他任意地方調(diào)用類的擴展都需要得到類的實例

  • 當dispatch receiver和extension receiver中有相同的函數(shù)簽名,擴展方法優(yōu)先調(diào)用extension receiver中的方法,除非指定dispatch receiver

作用域問題

擴展方法可以直接獨立在頂級包中定義

package foo.bar
fun Baz.goo() { ... }

要在包外調(diào)用擴展方法需要import這個方法或者包

package com.example.usage

import foo.bar.goo // importing all extensions by name "goo"

// or
import foo.bar.* // importing everything from "foo.bar"

fun usage(baz: Baz) {
    baz.goo()
)

當你在兩個頂級包內(nèi)定義了函數(shù)簽名相同的函數(shù),比如

package A

fun String.ok():String {
  return "ok"
}

package B

fun String.ok():String{
  return "OK"
}

這種情況會直接報錯,如果在頂級包名和一個類中定義了相同的擴展函數(shù)

package A
fun String.ok():String{
  return "ok"
}

package B
class Foo(){
  fun String.ok():String{
    return "OK"
  }
}

package C
fun main(s:Array<String>){
  println("Ara you ok?".ok())
}

result:
ok // by Top level package

在類中以成員方式定義的擴展方法只能在類的作用域內(nèi)被調(diào)用

比如類Foo中,以及Foo的擴展函數(shù)中fun Foo.xxx(){"ok?".ok()})

如果是在Foo中調(diào)用ok,成員擴展優(yōu)先于頂級擴展,成員函數(shù)也優(yōu)先于擴展函數(shù)

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

fun C.foo() { println("extension") }

上面這個例子不論在任何使用調(diào)用foo函數(shù)結(jié)果都是member

擴展函數(shù)的靜態(tài)性

open class C //kotlin 默認不能繼承要加open修飾符

class D: C()

fun C.foo() = "c" //函數(shù)為第一公民當然可以對函數(shù)賦值,既返回c

fun D.foo() = "d"

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

printFoo(D())

result:
c 

上面這個例子說明了成員函數(shù)是靜態(tài)的在你聲明參數(shù)類型的時候就決定了使用哪個成員函數(shù)而不是取決于你傳入的實際值

對友元擴展

kotlin中沒有static關(guān)鍵字也沒有實際上的靜態(tài)概念,如果想像java那樣調(diào)用靜態(tài)方法和靜態(tài)成員可以用友元

class My{
  companion object(){
    val I = 1
    fun getNum():Int{
      return I
    }
  }
}

fun main(args:Array<String>){
  val num = My.getNum()
  val i = My.I
  println(num == i)
}

result:
true

友元也可以進行擴展只要像下面那樣寫

class MyClass {
    companion object { }  // will be called "Companion"
}

fun MyClass.Companion.foo() {
    // ...
}

擴展屬性

可以給任意類擴展一個屬性

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

但是擴展的屬性并不是實際上在類里插入變量,所以沒有好的方法給它分配實際的存儲空間,所以擴展成員不能被賦值只能重寫他的get方法,其實這就和一個函數(shù)一樣了

空類型接收器

kotlin 中默認的變量是 not null 的如果這個函數(shù)可能為null就要在類型后面加?比如String?就代表這個字符串有可能是null,而我們也可以給這種類型加擴展,那么我們處理null判斷的方法就可以寫成這樣的擴展形式,這樣你就可以放心的調(diào)用toString()而不用判斷是否為空

fun Any?.toString(): String {
    if (this == null) return "null"
    // after the null check, 'this' is autocast to a non-null type, so the toString() below
    // resolves to the member function of the Any class
    return toString()
}

Extension的應(yīng)用場景

  • 可以改造一些原生的class 而不用寫一個Utils類,比如給String加一個格式化日期擴展

    fun String().toDate(format:String = "yyyy-MM-dd HH:mm:ss") :String{
      val sdf = SimpleDateFormat(format)
      val date = sdf.parse(value)
      return date.toString()
    }
    
    fun main(args:Array<String>){
      println("UTC format Date".toDate())//2017-11-11 11:11:11
    }
    

    ?

  • 可以寫一些大部分類都通用的routine

    //例如:
    fun doSomething(){
      try{
        //doing
      }catch(e:Exception){
        //error
      }
    }
    //這樣的代碼在java中有大把而且很多人其實catch了之后也不會做什么特殊的處理當代碼中到處充斥這種結(jié)構(gòu)甚至嵌套好幾層其實無形增加的閱讀的難度和維護的難度
    //extension way
    fun <T> T.dowithTry(work:(T)->Unit){
        try{
          work(T)  
        }catch(e:Exception){
            //print error etc.
        }
    }
    
    fun <T:Closeable> T.dowithTry(work:(T)->Unit){
        try{
          work(T)  
        }catch(e:Exception){
            //print error etc.
        }finally{
            this.close()
        }
    }
    //那么你只要這么調(diào)用,甚至臉close都幫你做了
    fun main(arg:Array<String>){
        val output = File().outputStream()
      output.dowithTry{
            it -> 
          it.read()
          ....
        }
    }
    
  • 實現(xiàn)一些函數(shù)式編程的魔法

結(jié)語

擴展是個很有意思的東西發(fā)揮想想力能寫出很多提升效率和閱讀性擴展函數(shù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容