要看视频的能够直接去 哔哩哔哩 或者 YouTube 观看。不方便看视频的,下面文章搞起。web
Kotlin 有个特别好用的功能叫扩展,你能够给已有的类去额外添加函数和属性,并且既不须要改源码也不须要写子类。这就是今天这个视频的主题。另外不少人虽然会用扩展,但只会最基本的使用,好比就只用来写个叫 dp
的扩展属性来把 dp 值转成像素值:面试
val Float.dp
get() = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics ) ... val RADIUS = 200f.dp 复制代码
稍微高级一点就不太行了,尤为是扩展函数和函数引用混在一块儿的时候就更是瞬间蒙圈。若是你有这样的问题,这个视频应该能够帮到你。架构
你们好,我是扔物线朱凯。 在 Java 里咱们若是想作幂运算——也就是几的几回方——要用静态方法 pow(a, n)
app
Math.pow(2, 10); // 2 的 10 次方
复制代码
pow 这个词你可能不认识,其实它不是个完整的词,而是 power 的缩写,power 就是乘方的意思,哎中国人学程序常常还须要学英文好烦。这个 pow(a, n)
方法是 Math
类的一个静态方法,这类方法咱们用得比较多的是 max()
和 min()
编辑器
Math.max(1, 2); // 2
Math.min(1, 2); // 1 复制代码
比较两个数的大小,用静态方法很符合直觉;可是幂运算的话,静态方法就不如成员方法来得更直观了:ide
2.pow(10); // 要是 Java 里能这样写就行了
复制代码
但咱们只能选择静态方法。为何?很简单,由于 Integer、Float、Double 这几个类没提供这个方法,因此咱们只能用 Math 类的静态方法。函数
在 Kotlin 里,咱们用的不是 Java 的 Integer、Float、Double,而是另外几个名字相同或相像的 Kotlin 本身新创造的类。这几个类一样没有提供 pow()
这个函数,但好的是,咱们依然能够用看起来像是成员函数的方式来作幂运算。学习
2f.pow(10) // Kotlin 能够这么写
复制代码
为何?由于 Float.pow(n: Int)
是 Kotlin 给 Float
这个类增长的一个扩展函数:ui
// kotlin.util.MathJVM.kt
public actual inline fun Float.pow(n: Int): Float = nativeMath.pow(this.toDouble(), n.toDouble()).toFloat() 复制代码
在声明一个函数的时候在函数名的左边写个类名再加个点,你就能对这个类的对象调用这个函数了。这种函数就叫扩展函数,Extension Functions。就好像你钻到这个类的源码里,改了它的代码,给它增长了一个新的函数同样。虽然事实上不是,但用起来基本同样。具体区别我等会儿说。this
这种用法给咱们的开发带来了极大的便利,咱们能够用它来作不少事。
举个例子?
class MainActivity : AppCompatActivity() {
val model: MyViewModel by viewModels() ... } 复制代码
而不须要重写 Activity 类。
扩展函数写在哪均可以,但写的位置不一样,做用域就也不一样。所谓做用域就是说你能在哪些地方调用到它。 最简单的写法就是把它写成 Top Level 也就是顶层的,让它不属于任何类,这样你就能在任何类里使用它。这也和成员函数的做用域很像——哪里能用到这个类,哪里就能用到类里的这个函数:
package com.rengwuxian
fun String.method1(i: Int) { ... } ... "rengwuxian".method1(1) 复制代码
有一点要注意了:这个函数属于谁?属于函数名左边的类吗?并非的,它是个 Top-level Function,它谁也不属于,或者说它只属于它所在的 package。 那它为何能够被这个类的对象调用呢?——由于它在函数名的左边呀!在 Kotlin 里,当你给声明的函数名左边加上一个类名的时候,表示你要给这个函数限定一个 Receiver——直译的话叫接收者,其实也就是哪一个类的对象能够调用这个函数。虽说你是个 Top-level Function,不属于任何类——确切地说是,不是任何一个类的成员函数——但我要限制只有经过某个类的对象才能调用你。这就是扩展函数的本质。 那这……和成员函数有什么区别吗?这种奇怪又绕脑子的知识有什么用吗?听我继续讲。
除了写成 Top Level 的,扩展函数也能够写在某个类里:
class Example {
fun String.method2(i: Int) { ... } } 复制代码
而后你就能够在这个类里调用这个函数,但必须使用那个前缀类的对象来调用它:
class Example {
fun String.method2(i: Int) { ... } ... "rengwuxian".method2(1) // 能够调用 } 复制代码
看起来……有点奇怪了。这个函数这么写,它究竟是属于谁的呀?属于外部的类仍是左边前缀的类? 属于谁?这个「属于谁」其实有点模糊的,我须要问再明确点:它是谁的成员函数?固然是外部的类的成员函数了,由于它写在它里面嘛,对吧?那函数名左边的是什么?刚才我刚说过,它是这个函数的 Receiver,对吧?也就是谁能够去调用它。 因此它既是外部类的成员函数,又是前缀类的扩展函数。 这种既是成员函数、又是扩展函数的函数,它们的用法跟 Top Level 的扩展函数同样,只是因为它同时仍是成员函数,因此只能在它所属的类里面被调用,到了外面就不能用了:
class Example {
fun String.method2(i: Int) { ... } ... "rengwuxian".method2(1) // 能够调用 } "rengwuxian".method2(1) // 类的外部不能调用 复制代码
这个……也好理解吧?你为何要把扩展函数写在类的里面?不就是为了让它不要被外界看见形成污染吗,是吧?
在以前 Lambda 那一期视频里,我说过函数是可使用双冒号被指向的对吧:
Int::toFloat
复制代码
我当时也讲了,其实指向的并非函数自己,而是和函数等价的一个对象,这也是为何你能够对这个引用调用 invoke(),却不能对函数自己调用:
(Int::toFloat)(1) // 等价于 1.toFloat()
Int::toFloat.invoke(1) // 等价于 1.toFloat() 1.toFloat.invoke() // 报错 复制代码
可是为了简单起见,咱们一般能够把这个「指向和函数等价的对象的引用」称做是「指向这个函数的引用」,这个问题不大。那么咱们基于这个叫法继续说。 普通函数能够被指向,扩展函数一样也是能够被指向的:
fun String.method1(i: Int) {
} ... String::method1 复制代码
不过若是这个扩展函数不是 Top-Level 的,也就是说若是它是某个类的成员函数,它就不能被引用了:
class Extensions {
fun String.method1(i: Int) { ... } ... String::method1 // 报错 } 复制代码
为何?你想啊,一个成员函数怎么引用:类名加双冒号加函数名对吧?扩展函数呢?也是类名加双冒号加函数名对吧?只不过此次是 Receiver 的类名。那成员扩展函数呢?还用类名加双冒号加函数名呗?可是……用谁的类名?是这个函数所属的类名,仍是它的 Receiver 的类名?这是有歧义的,因此 Kotlin 就干脆不准咱们引用既是成员函数又是扩展函数的函数了,一了百了。 一样,跟普通函数的引用同样,扩展函数的引用也能够被调用,直接调用或者用 invoke() 均可以,不过要记得把 Receiver 也就是接收者或者说调用者填成第一个参数:
(String::method1)("rengwuxian", 1)
String::method1.invoke("rengwuxian", 1) // 以上两句都等价于: "rengwuxian".method1(1) 复制代码
一样的,扩展函数的引用也能够赋值给变量:
val a: String.(Int) -> Unit = String::method1
复制代码
而后你再拿着这个变量去调用,或者再次传递给别的变量,都是能够的:
"rengwuxian".a(1)
a("rengwuxian", 1) a.invoke("rengwuxian", 1) 复制代码
另外你们可能会发现,当你拿着一个函数的引用去调用的时候,不论是一个普通的成员函数仍是扩展函数,你都须要把 Receiver 也就是接收者或者调用者做为第一个参数填进去。
(String::method1)("rengwuxian", 1) // 等价于 "rengwuxian".method1(1)
(Int::toFloat)(1) // 等价于 1.toFloat() 复制代码
为何?由于你拿到的是函数引用而不是调用者的对象,因此没办法在左边写上调用者啊,是吧? 因此 Kotlin 要想支持让咱们拿着函数的引用去调用,就必须给个途径让咱们提供调用者。那提供怎样的途径呢?最终 Kotlin 给咱们的方案就是:在这种调用方式下,增长一个函数参数,让咱们把第一个参数的位置填上调用者。这样,咱们就能够用函数的引用来调用成员函数和扩展函数了。但同时,又有一个问题我不知道大家发现没有: 既然有 Receiver 的函数能够以无 Receiver 的方式来调用,那……它能够赋值给无 Receiver 的函数类型的变量吗?
val b: (String, Int) -> Unit = String::method1 // 这样能够吗?
复制代码
答案是,能够的。在 Kotlin 里,每个有 Receiver 的函数——其实就是成员函数和扩展函数——它的引用均可以赋值给两种不一样的函数类型变量:一种是有 Receiver 的,一种是没有 Receiver 的:
val a: String.(Int) -> Unit = String::method1
val b: (String, Int) -> Unit = String::method1 复制代码
这两种写法都是合法的。为何?由于有用啊,是吧?有什么用我刚讲过,忘了的倒个带。
蔡依林:「终于看开……」
并且一样的,这两种类型的变量也能够互相赋值来进行转换:
val a: String.(Int) -> Unit = String::method1
val b: (String, Int) -> Unit = String::method1 val c: String.(Int) -> Unit = b val d: (String, Int) -> Unit = a 复制代码
懵了?懵就对了,不要急,继续看,知识掌握住了,下去慢慢试慢慢琢磨。
继续讲。 既然这两种类型的变量能够互相赋值来转换,那不就是说无 Receiver 的函数引用也能够赋值给有 Receiver 的变量? 这样的话,是否是一个普通的无 Receiver 的函数也能够直接赋值给有 Receiver 的变量?
fun method3(s: String, i: Int) {
} ... val e: (String, Int) -> Unit = ::method3 val f: String.(Int) -> Unit = ::method3 // 这种写法也行哦 复制代码
哇塞,没有报错! 是的,这样赋值也是能够的。 经过这些类型的互相转换,你能够把一个原本没有 Receiver 的函数变得能够经过 Receiver 来调用:
fun method3(s: String, i: Int) {
} ... val f: String.(Int) -> Unit = ::method3 "rengwuxian".method3(1) // 不容许调用,报错 "rengwuxian".f(1) // 能够调用 复制代码
这就很爽了哈? 固然了你也能够反向操做,去把一个有 Receiver 的函数变得不能用 Receiver 调用:
fun String.method1(i: Int) {
} ... val b: (String, Int) -> Unit = String::method1 "rengwuxian".method1(1) // 能够调用 "rengwuxian".b(1) // 不容许调用,报错 复制代码
这样收窄功能好像没什么用哈?不过我仍是要把这个告诉你,由于这样你的知识体系才是完整的。
说到完整啊,每一个作 Android 的人都应该把本身的支撑体系扩充一下,让本身的技能树变完整,你才能百毒不侵,工做和面试都不怕。那要怎么完整呢?最好的方式就是来学习一下个人 Android 高级进阶系列化课程。扫描屏幕上的二维码,加个人助教,更多课程相关的信息以及我更多的知识输出渠道找 TA 了解。
除了扩展函数,Kotlin 的扩展还包括扩展属性。它跟扩展函数是一个逻辑,就是在声明的属性左边写上类名加点,这就是一个扩展属性了,英文原名叫 Extension Property。
val Float.dp
get() = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics ) ... val RADIUS = 200f.dp 复制代码
它的用法和扩展函数同样,但少了扩展函数在引用上以及 Receiver 上的一些比较绕的问题,因此很简单,你本身去研究吧。有些东西写成扩展属性是比扩展函数要更加直观和方便的,因此虽然它很简单,但研究一下绝对有好处。
此次讲的内容挺多的,但其实也很简单,主要就这么几点:扩展函数、扩展函数的引用、有无 Receiver 的函数类型的转换以及扩展属性。记不住的把视频多刷几遍,不要怕,我在个人课程里也常常跟个人学员说:你把每节课多刷几遍,别嫌费时间,又不是电视剧,知识密度这么大的课程你多看几遍只赚不亏。
好了今天的视频就到这里,若是你喜欢个人内容,欢迎点赞留言收藏分享。扫码关注我,不错过个人任何新内容。我是扔物线,我不和你比高低,我只助你成长。咱们下期见。
本文使用 mdnice 排版