302 kotlin的扩展函数和扩展属性

kotlin扩展方法、属性

1.概念

kotlin支持在不修改类代码的情况下,动态为类添加属性(扩展属性)和方法(扩展方法)。

2.扩展方法

扩展方法执行静态解析(编译时),成员方法执行动态解析(运行时)

(1)语法格式

​ 定义一个函数,在被定义的函数前面添加“类名.”,该函数即为该类名对应类的拓展方法。

fun main(args: Array<String>) {
    val extensionClass = ExtensionClass()
    //调用拓展方法
    extensionClass.test()
}
//定义一个空类
class ExtensionClass
//为该空类定义一个拓展方法test()方法
fun ExtensionClass.test() = println("我是ExtensionClass的拓展方法")

(2)成员方法优先

如果被扩展的类的扩展方法与该类的成员方法名字和参数一样,该类对象调用该方法时,调用的会是成员方法。

fun main(args: Array<String>) {
    val extension = ExtensionTest()
    //此处调用的会是成员方法
    extension.test()
}

class ExtensionTest {
    fun test() = print("成员方法")
}
//该方法不会被调用
fun ExtensionTest.test() = println("扩展方法")

(3)为系统类添加拓展方法(以String为例)

fun main(args: Array<String>) {
    val str = "123456"
    //调用String的拓展方法
    println(str.lastIndex())
}
//为String定义一个拓展方法
fun String.lastIndex() = length - 1

(4)扩展实现原理

java是一门静态语言,无法动态的为类添加方法、属性,除非修改类的源码,并重新编译该类。

​ kotlin扩展属性、方法时看起来是为该类动态添加了成员,实际上并没有真正修改这个被扩展的类,kotlin实质是定义了一个函数,当被扩展的类的对象调用扩展方法时,kotlin会执行静态解析,将调用扩展函数静态解析为函数调用。

静态解析:根据调用对象、方法名找到拓展函数,转换为函数调用。

如(2)str.lastIndex()方法执行的过程为:
①检查str类型(发现为String类型);

​ ②检查String是否定义了lastIndex()成员方法,如果定义了,编译直接通过;

​ ③如果String没定义lastIndex()方法,kotlin开始查找程序是否有为String类扩展了lastIndex()方法(即是否有fun String.lastIndex()),如果有定义该扩展方法,会执行该扩展方法;

​ ④既没定义lastIndex()成员方法也没定义扩展方法,编译自然不通过。

(5)静态解析调用扩展方法注意点

由于静态调用扩展方法是在编译时执行,因此,如果父类和子类都扩展了同名的一个扩展方法,引用类型均为父类的情况下,会调用父类的扩展方法。

/**
 * 拓展属性、方法
 */
fun main(args: Array<String>) {
    val father : ExtensionTest = ExtensionTest()
    father.test()//调用父类扩展方法
    val child1 : ExtensionTest = ExtensionSubTest()
    child1.test()//引用类型为父类类型,编译时静态调用的还是父类的扩展方法
    val child2 : ExtensionSubTest = ExtensionSubTest()
    child2.test()//此时才是调用子类的扩展方法
}

/**
 * 父类
 */
open class ExtensionTest

/**
 * 子类
 */
class ExtensionSubTest : ExtensionTest()

/**
 * 父类扩展一个test方法
 */
fun ExtensionTest.test() = println("父类扩展方法")

/**
 * 子类扩展一个test方法
 */
fun ExtensionSubTest.test() = println("子类扩展方法")

(6)可空类型扩展方法(以扩展equals方法为例)

kotlin允许扩展可空类型扩展方法,这样,null也能调用该方法。

fun main(args: Array<String>) {
    val a: Any? = null
    val b: Any? = null
    println(a.equals(b))
}

fun Any?.equals(any: Any?): Boolean = this != null && any != null && any.equals(this)

3.扩展属性

(1)概念

kotlin允许动态为类扩展属性,扩展属性是通过添加get、set方法实现,没有幕后字段(filed)。

​ 扩展属性也没有真的为该类添加了属性,只能说是为该类通过get、set方法计算出属性。

​ 限制:①扩展属性不能有初始值;②扩展属性不能用filed关键字访问幕后字段;③val必须提供get方法,var必须提供get和set方法。

(2)定义扩展属性

fun main(args: Array<String>) {
    val extensionTest = ExtensionTest("a", "b")
    println(extensionTest.param1)//a
    println(extensionTest.param2)//b
    println(extensionTest.extensionParam)//a-b
}

/**
 * 定义一个类,包含属性param1、属性param2
 */
class ExtensionTest(var param1: String, var param2: String)

/**
 * 为该类扩展属性extensionParam
 */
var ExtensionTest.extensionParam: String
    set(value) {
        param1 = "param1$value"
        param1 = "param2$value"
    }
    get() = "$param1-$param2"

4.以类成员方式定义扩展

在某个类里面为其他类定义扩展方法、属性,该扩展的方法,只能在该类中通过被扩展的类的对象调用扩展方法。

​ 以类成员方式定义的扩展,属于被扩展的类,因此在扩展方法直接调用被扩展的类的成员(this可以省略),同时因为它位于所在类中,因此又可以直接调用所在类的成员。

fun main(args: Array<String>) {
    val extensionTest = ExtensionTest()
    val extensionTest2 = ExtensionTest2()
    extensionTest2.info(extensionTest)
}

/**
 * 定义一个类包含test方法
 */
class ExtensionTest {
    fun test() = println("ExtensionTest的test方法")
}

/**
 * 定义一个类包含test方法,包含ExtensionTest的一个扩展方法
 */
class ExtensionTest2 {
    val a = "a"
    fun test() = println("ExtensionTest2的test方法")
    fun ExtensionTest.func() {
        println(a)//调用扩展类的成员
        test()//调用被扩展类的成员,相当于this.test()
        this@ExtensionTest2.test()//同名的需要用this@类名的方式来调用
    }

    fun info(extensionTest: ExtensionTest) {
        extensionTest.func()
    }
}

5.带接收者的匿名扩展函数

(1)概念

扩展方法(fun 类名.方法名())去掉方法名就是所谓的带接收者的匿名扩展函数,接收者就是类本身,形如:fun Int.() : Int。

fun main(args: Array<String>) {
    val extensionTest = ExtensionTest()
    println(extensionTest.noNameExtensionFun("向带接收者的匿名函数传入的参数"))//使用匿名扩展函数
}

/**
 * 定义一个空类
 */
class ExtensionTest

/**
 * 为空类定义一个带接收者的匿名扩展函数
 */
var noNameExtensionFun = fun ExtensionTest.(param: String): String {
    println(param)
    return "我是来自带接收者的匿名扩展函数的返回值"
}

(2)带接收者的匿名扩展函数的函数类型

​ 与普通函数一样,匿名扩展方法也有函数类型,(1)中的函数类型为:ExtensionTest.(String) -> String

(3)带接收者的匿名扩展函数与lambda表达式

如果能根据上下文推断出接收者类型,则可以使用lambda表达式

fun main(args: Array<String>) {
    test {
        println(it)
        "匿名扩展函数返回值"
    }
}

/**
 * 定义一个空类
 */
class ExtensionTest

/**
 * 定义一个函数,形参为ExtensionTest.(String) -> String类型,相当于同时为ExtensionTest类扩展了一个匿名扩展函数
 */
fun test(fn: ExtensionTest.(String) -> String) {
    val extensionTest = ExtensionTest()
    println("调用匿名扩展函数:${extensionTest.fn("匿名扩展函数传入形参")}")
}

6.扩展使用场景

扩展极大的增加了程序的灵活性,java如果想对一个类扩展某些属性,必须通过继承的方式等实现,kotlin使用语法直接可以动态的扩展,能更方便组织一些工具方法等。

fun main(args: Array<String>) {
    "打印日志".log()
}

/**
 * 为String字符串添加一个打印日志的扩展方法
 */
fun String.log() {
    println(this)
}

扩展函数和扩展属性的实现

我们都知道,Java 中,只有一个类型的成员属性和成员方法才能用“对象.属性 / 方法()”的方式调用,一个类型的对象是绝对不可能通过这种方法调用其他类里定义的方法(除非存在继承或实现关系)。而 Kotlin 提供的扩展函数和扩展属性打破了这一规则,它是怎么实现的呢?

首先看例子:

// Test.kt
fun <T> MutableList<T>.swap(indexA: Int, indexB: Int) {
    val temp = this[indexA]
    this[indexA] = this[indexB]
    this[indexB] = temp
}

val Int.isOdd: Boolean
    get() = this and 1 == 1

我们在这里定义一个扩展函数 swap,它的接收者是 MutableList,作用是调换传入的两个索引对应的值。然后给 Int 类定义了一个扩展属性 isOdd,用来检查这个 Int 是不是奇数。

这时我们就可以这样调用它们了:

val list = mutableListOf(1, 2, 3)
list.swap(0, 1)
println(list)
// [2, 1, 3]

val n = 3
println(n.isOdd)
// true

但想要在 Java 中调用它们,就要这么写了:

// import TestKt
List<Integer> list = new ArrayList<>();
list.add(1); list.add(2); list.add(3);
TestKt.swap(list, 0, 1);
System.out.println(list);

int n = 3;
println(TestKt.isOdd(n));

实际上,所有的扩展函数和扩展属性都会被编译成一个方法,这个方法的第一个参数就是扩展的接收者,然后才是其它各个参数。对于扩展属性来说 ,因为编译后这个属性并不存在,所以不能像一般的类属性那样对它进行初始化,而是要自定义 getter 和 setter 来访问它。

为什么要用扩展函数和扩展属性

Java 里有许多工具类,比如 Collections、Arrays、Objects 等等,它们提供了一系列静态方法来充当工具函数,通过参数传入被操作的对象,既不直观又冗长无比。

比如对于 Integer.parseInt(String s),Kotlin 就用一个扩展函数替代了它:

inline fun String.toInt() = java.lang.Integer.parseInt(this)

虽然还是调用这个方法,但这样定义有两个好处,一是减少了代码量,二是形成了一个统一的标准,所有其他基本类型都可以重载这个方法,实现同一个行为。

从另一个角度来看,Kotlin 鼓励开发者 尽量精简类的定义,一个类只定义框架,工具函数可以通过外部扩展一点点地添加,尽量不改动原有的类。

kotlin的扩展函数和扩展属性

简述: 今天带来的是Kotlin浅谈系列的第五弹,这讲主要是讲利用Kotlin中的扩展函数特性让我们的代码变得更加简单和整洁。扩展函数是Kotlin语言中独有的新特性,利用它可以减少很多的样板代码,大大提高开发的效率;此外扩展函数的使用也是非常简单的。我会从以下几个方面阐述Kotlin中的扩展函数。

1、为什么要使用Kotlin中的扩展函数?
2、怎么去使用扩展函数和扩展属性?
3、什么是扩展函数和属性?
4、扩展函数和成员函数区别
5、扩展函数不可以被重写

一、为什么要使用Kotlin中的扩展函数

我们都知道在Koltin这门语言可以与Java有非常好的互操作性,所以扩展函数这个新特性可以很平滑与现有Java代码集成。甚至纯Kotlin的项目都可以基于Java库,甚至Android中的一些框架库,第三方库来构建。扩展函数非常适合Kotlin和Java语言混合开发模式。在很多公司一些比较稳定良好的库都是Java写,也完全没必要去用Kotlin语言重写。但是想要扩展库的接口和功能,这时候扩展函数可能就会派上用场。使用Kotlin的扩展函数还有一个好处就是没有副作用,不会对原有库代码或功能产生影响。先来看下扩展函数长啥样

  • 给TextView设置加粗简单的例子
//扩展函数定义
fun TextView.isBold() = this.apply { 
    paint.isFakeBoldText = true
}

//扩展函数调用
activity.find<TextView>(R.id.course_comment_tv_score).isBold()

二、怎么去使用扩展函数和扩展属性

  • 1、扩展函数的基本使用
    只需要把扩展的类或者接口名称,放到即将要添加的函数名前面。这个类或者名称就叫做接收者类型,类的名称与函数之间用"."调用连接。this指代的就是接收者对象,它可以访问扩展的这个类可访问的方法和属性。

注意: 接收者类型是由扩展函数定义的,而接收者对象正是这个接收者类型的对象实例,那么这个对象实例就可以访问这个类中成员方法和属性,所以一般会把扩展函数当做成员函数来用。

  • 2、扩展属性的基本使用
    扩展属性实际上是提供一种方法来访问属性而已,并且这些扩展属性是没有任何的状态的,因为不可能给现有Java库中的对象额外添加属性字段,只是使用简洁语法类似直接操作属性,实际上还是方法的访问。
//扩展属性定义
var TextView.isBolder: Boolean
    get() {//必须定义get()方法,因为不能在现有对象添加字段,也自然就没有了默认的get()实现
        return this.paint.isFakeBoldText
    }
    set(value) {
        this.paint.isFakeBoldText = true
    }
//扩展属性调用
activity.find<TextView>(R.id.course_comment_tv_score).isBolder = true

注意:

  • 扩展属性和扩展函数定义类似,也有接收者类型和接收者对象,接收者对象也是接收者类型的一个实例,一般可以把它当做类中成员属性来使用。
  • 必须定义get()方法,在Kotlin中类中的属性都是默认添加get()方法的,但是由于扩展属性并不是给现有库中的类添加额外的属性,自然就没有默认get()方法实现之说。所以必须手动添加get()方法。
  • 由于重写了set()方法,说明这个属性访问权限是可读和可写,需要使用var

三、什么是扩展函数和属性

我们从上面例子可以看出,kotlin的扩展函数真是强大,可以毫无副作用给原有库的类增加属性和方法,比如例子中TextView,我们根本没有去动TextView源码,但是却给它增加一个扩展属性和函数。具有那么强大功能,到底它背后原理是什么?其实很简单,通过decompile看下反编译后对应的Java代码就一目了然了。

  • 1、扩展函数实质原理
    扩展函数实际上就是一个对应Java中的静态函数,这个静态函数参数为接收者类型的对象,然后利用这个对象就可以访问这个类中的成员属性和方法了,并且最后返回一个这个接收者类型对象本身。这样在外部感觉和使用类的成员函数是一样的。
public final class ExtendsionTextViewKt {//这个类名就是顶层文件名+“Kt”后缀,这个知识上篇博客有详细介绍
   @NotNull
   public static final TextView isBold(@NotNull TextView $receiver) {//扩展函数isBold对应实际上是Java中的静态函数,并且传入一个接收者类型对象作为参数
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      $receiver.getPaint().setFakeBoldText(true);//设置加粗
      return $receiver;//最后返回这个接收者对象自身,以致于我们在Kotlin中完全可以使用this替代接收者对象或者直接不写。
   }
}

  • 2、Java中调用Kotlin中定义的扩展函数
    分析完Kotlin中扩展函数的原理,我们也就很清楚,如何在Java中去调用Kotlin中定义好的扩展函数了,实际上使用方法就是静态函数调用,和我们之前讲的顶层函数在Java中调用类似,不过唯一不同是需要传入一个接收者对象参数。
ExtendsionTextViewKt.isBold(activity.findViewById(R.id.course_comment_tv_score));//直接调用静态函数

  • 3、扩展属性实质原理
    扩展属性实际上就是提供某个属性访问的set,get方法,这两个set,get方法是静态函数,同时都会传入一个接收者类型的对象,然后在其内部用这个对象实例去访问和修改对象所对应的类的属性。
public final class ExtendsionTextViewKt {
   //get()方法所对应生成静态函数,并且传入一个接收者类型对象作为参数
   public static final boolean isBolder(@NotNull TextView $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      return $receiver.getPaint().isFakeBoldText();
   }
   //set()方法所对应生成静态函数,并且传入一个接收者类型对象作为参数和一个需要set的参数
   public static final void setBolder(@NotNull TextView $receiver, boolean value) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      $receiver.getPaint().setFakeBoldText(true);
   }
}

  • 4、Java中调用Kotlin中定义的扩展属性
    Java调用Kotlin中定义的扩展属性也很简单,就相当于直接调用生成的set(),get()方法一样。
    ExtendsionTextViewKt.setBolder(activity.findViewById(R.id.course_comment_tv_score), true);

四、扩展函数和成员函数区别

说到扩展函数和成员函数的区别,通过上面例子我们已经很清楚了,这里做个归纳总结:

  • 1、扩展函数和成员函数使用方式类似,可以直接访问被扩展类的方法和属性。(原理: 传入了一个扩展类的对象,内部实际上是用实例对象去访问扩展类的方法和属性)

  • 2、扩展函数不能打破扩展类的封装性,不能像成员函数一样直接访问内部私有函数和属性。(原理: 原理很简单,扩展函数访问实际是类的对象访问,由于类的对象实例不能访问内部私有函数和属性,自然扩展函数也就不能访问内部私有函数和属性了)

  • 3、扩展函数实际上是一个静态函数是处于类的外部,而成员函数则是类的内部函数。

  • 父类成员函数可以被子类重写,而扩展函数则不行

五、扩展函数不可以被重写

在Kotlin和Java中我们都知道类的成员函数是可以被重写的,子类是可以重写父类的成员函数,但是子类是不可以重写父类的扩展函数。

open class Animal {
    open fun shout() = println("animal is shout")//定义成员函数
}

class Cat: Animal() {
    override fun shout() {
        println("Cat is shout")//子类重写父类成员函数
    }
}

//定义子类和父类扩展函数
fun Animal.eat() = println("Animal eat something")

fun Cat.eat()= println("Cat eat fish")

//测试
fun main(args: Array<String>) {
    val animal: Animal = Cat()
    println("成员函数测试: ${animal.shout()}")
    println("扩展函数测试: ${animal.eat()}")
}


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