Kotlin Extension 使用要点

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()
          ....
        }
    }
    
  • 实现一些函数式编程的魔法

结语

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容