Kotlin Extension 使用要点

作者: Jocker_h | 来源:发表于2017-09-12 11:33 被阅读901次

    Extension 既扩展方法,若写过动态语言就一定知道

    class Foo{}
    
    foo = new Foo()
    foo.func = funtion (args){
      //do something
    }
    
    之后就可以调用foo.func()函数这就是动态扩展
    

    上面的例子是给实例扩展当然动态语言给类扩展也是没有问题的,而kotlin中可以对类进行扩展

    以类成员函数的方式声明

    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中的第一公民是函数。

    从上面的代码中我想说明以下几点扩展函数的特性

    • 扩展函数是对类的扩展,对于Father来说相当于在自己里面定义了buyGucci函数

    • 同时扩展函数中隐含着两个引用,既有Mather的实例的引用(dispatch receiver

      )又有father实例的引用(extension receiver)所以可以直接调用两个类的函数以及属性

    • 在其他任意地方调用类的扩展都需要得到类的实例

    • 当dispatch receiver和extension receiver中有相同的函数签名,扩展方法优先调用extension receiver中的方法,除非指定dispatch receiver

    作用域问题

    扩展方法可以直接独立在顶级包中定义

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

    要在包外调用扩展方法需要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()
    )
    

    当你在两个顶级包内定义了函数签名相同的函数,比如

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

    这种情况会直接报错,如果在顶级包名和一个类中定义了相同的扩展函数

    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
    

    在类中以成员方式定义的扩展方法只能在类的作用域内被调用

    比如类Foo中,以及Foo的扩展函数中fun Foo.xxx(){"ok?".ok()})

    如果是在Foo中调用ok,成员扩展优先于顶级扩展,成员函数也优先于扩展函数

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

    上面这个例子不论在任何使用调用foo函数结果都是member

    扩展函数的静态性

    open class C //kotlin 默认不能继承要加open修饰符
    
    class D: C()
    
    fun C.foo() = "c" //函数为第一公民当然可以对函数赋值,既返回c
    
    fun D.foo() = "d"
    
    fun printFoo(c: C) {
        println(c.foo())
    }
    
    printFoo(D())
    
    result:
    c 
    

    上面这个例子说明了成员函数是静态的在你声明参数类型的时候就决定了使用哪个成员函数而不是取决于你传入的实际值

    对友元扩展

    kotlin中没有static关键字也没有实际上的静态概念,如果想像java那样调用静态方法和静态成员可以用友元

    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方法,其实这就和一个函数一样了

    空类型接收器

    kotlin 中默认的变量是 not null 的如果这个函数可能为null就要在类型后面加?比如String?就代表这个字符串有可能是null,而我们也可以给这种类型加扩展,那么我们处理null判断的方法就可以写成这样的扩展形式,这样你就可以放心的调用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的应用场景

    • 可以改造一些原生的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了之后也不会做什么特殊的处理当代码中到处充斥这种结构甚至嵌套好几层其实无形增加的阅读的难度和维护的难度
      //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()
          }
      }
      //那么你只要这么调用,甚至脸close都帮你做了
      fun main(arg:Array<String>){
          val output = File().outputStream()
        output.dowithTry{
              it -> 
            it.read()
            ....
          }
      }
      
    • 实现一些函数式编程的魔法

    结语

    扩展是个很有意思的东西发挥想想力能写出很多提升效率和阅读性扩展函数

    相关文章

      网友评论

        本文标题:Kotlin Extension 使用要点

        本文链接:https://www.haomeiwen.com/subject/ejhcsxtx.html