往期文章
前言
扩展(Extension),可以说是 Kotlin 里最有意思的特性,没有之一。
本文会系统的讲解 Kotlin 扩展函数和扩展属性以及比较难懂的扩展作用域和扩展静态解析,最后再搭配一个实战环节,将扩展函数跟前面讲的高阶函数结合到一起。
前期准备
- 将 Android Studio 版本升级到最新
 - 将我们的 Demo 工程 clone 到本地,用 Android Studio 打开:
github.com/chaxiu/Kotl… - 切换到分支:
chapter_06_extension - 强烈建议各位小伙伴小伙伴跟着本文一起实战,实战才是本文的精髓
 
正文
1. 扩展是什么?
Kotlin 的扩展,用起来就像是:能给一个类新增功能,这个新增的功能:可以是函数,也可以是属性。
借助 Kotlin 扩展,我们能轻易的写出这样的代码:
1  | arduino复制代码// 扩展函数  | 
以上的代码,看起来就像是我们修改了原本 String 并且往里面加了方法和属性: log(), isNullOrBlank。
初次见到扩展这个特性的时候,我真的被惊艳到了。虽然扩展不是 Kotlin 独有的特性(别的现代语言也有),但是,Kotlin 能在兼容 Java 的同时引入这样的特性,那就真的很了不起了。
2. 顶层扩展 (Top Level Extension)
顶层扩展,是最常用的扩展方式,它的定义方式也很简单,以上面的两行代码为例,我们看看它们分别应该怎么定义吧。
1  | kotlin复制代码// BaseExt.kt  | 
3. 顶层扩展的原理是什么?
要理解顶层扩展的实现原理,直接看字节码对应的 Java 即可,前面的文章已经讲过如何将 Kotlin 代码反编译成 Java,我们直接看结果:
1  | java复制代码public static final void log(String $this$log) {  | 
顶层扩展的本质,其实就是 Java 的静态方法,这跟我们在 Java 中经常写的 Utils 类其实是一个原理。Kotlin 的顶层扩展用着感觉很神奇,但它的原理异常简单。这一切都是因为 Kotlin 编译器帮我们做了一层封装和转换。
有的人可能会嗤之以鼻的说“这不就是语法糖嘛”,但我从中看到的是 Kotlin 这种追求简洁和生产力的设计思想。
4. 类内扩展 (Declaring extensions as members)
Package 级别的顶层扩展理解起来很简单,类内扩展会稍微复杂些。
类内扩展(Declaring extensions as members) 在官方中文站的翻译是:扩展声明为成员,这个翻译虽然更接近本质,但太僵硬了,因此我在这里用 类内扩展 指代它。
类内扩展的写法跟顶层扩展是一模一样的,区别在于它在其他类的里面。让我们来看一个例子:
1  | kotlin复制代码// 被扩展的类  | 
5. 扩展小结:
顶层扩展它不能定义在类内,它的作用域是 Package 级别的,能导包就能用类内扩展它定义在其他类内,它的作用与局限在该类内类内扩展的优势在于,它既能访问被扩展类(Host),也能访问它所在的类(Test)扩展并没有实际修改被扩展的类,因此我们仍然只能访问类里的public方法和属性
6. 类内扩展的原理是什么?
我们直接看反编译后的 Java:
1  | java复制代码// Host 类并没有新增任何属性和方法  | 
我们回过头来看 类内扩展 的英文:(Declaring extensions as members),这非常接近它的本质。看到这里,各位应该明白这两个名字的差别:类内扩展描述的是表象;扩展声明为成员描述的是原理。
另外,在上面这个案例中,Test 叫做分发接收者(Dispatch Receiver),Host 叫做扩展接受者(Extension Receiver)。这……是不是好像在哪听过类似的名字?对!这里跟上一章节:高阶函数带接收者的函数类型相呼应了。
7. 扩展函数的类型是什么?
上一章节讲带接收者的函数类型的时候,我讲过这样一句话:
从外表上看,带接收者的函数类型,就等价于成员函数(也等价于扩展函数)。但从本质上讲,它仍是通过编译器注入 this 来实现的。
一个表格来总结:
所以说,带接收者的函数类型和扩展函数的语法设计也是一样的。
下面是我在 Demo 里写的验证代码,感兴趣的小伙伴可以去 TestExt.kt 实际运行一下:
1  | kotlin复制代码fun testFunctionType() {  | 
8. 扩展是静态的
扩展是静态的。
这句话的潜台词是:扩展不支持多态。看这个代码案例很容易就能理解:
1  | kotlin复制代码open class Shape  | 
这个特性虽然反直觉,但是很容易理解,以后我们使用过程当中注意一下就好。
以上代码的具体细节可以看我这个 GitHub Commit。
9. 类内扩展 override,扩展函数冲突
这部分是扩展函数相对难理解的部分,文字不容易解释,只有实际运行代码通过反编译才能弄清楚,请到 Demo 工程中找到 TestExtAsMember.kt 运行代码,然后反编译思考一下。相关解释我已经写到注释里了。代码案例也是直接用的官方文档里的,这个例子设计的很巧妙。
TestExtAsMember.kt 的代码如下:
1  | kotlin复制代码open class Base { }  | 
以上代码的具体细节可以看我这个 GitHub Commit。
6. 实战
学了这么多理论,终于到我们的实战环节了。
7. 扩展函数 + SharedPreferences
还记得 Java 的 SharedPreferences 有多麻烦吗?这种模版代码我们是否写过很多?
1  | java复制代码SharedPreferences sharedPreferences= getSharedPreferences("data",Context.MODE_PRIVATE);  | 
Java 时代我们可以封装类似 PreferencesUtils 来避免模版代码。而 Kotlin 的扩展函数能让我们的代码看起来更加的简洁。接下来,我们为 SharedPreferences 增加一个扩展函数:
1  | kotlin复制代码fun SharedPreferences.edit(  | 
这个扩展函数很简单,我们直接看怎么用它吧。
1  | kotlin复制代码// MainActivity.kt  | 
是不是清爽很多?我们终于有地方缓存 API 请求了。😂
注:另外,我们还可以结合 Kotlin 的其他特性将 SharedPreferences 封装的更加彻底,这个我们下一篇文章会讲哈。
8. 扩展函数 + Spannable
Java 里要写一个复杂的 SpannableString,是件很痛苦的事情,我随手搜一段老代码,不知能否唤起你的痛苦记忆:
1  | java复制代码SpannableString spannableString = new SpannableString("设置各种不同的字体风格:叶应是叶");  | 
让我们看看借助 Kotlin 的扩展函数能做出什么样的事情吧:
这是我们接下来要实现的效果,虽然它看着是4行文字,但它却是在一个 TextView 里展示的:
在 Java 里要实现这样一个效果得费不少力气,但借助 Kotlin 扩展函数,我们写一个这样的效果简直是不费吹灰之力:
1  | kotlin复制代码// MainActivity.kt  | 
对应的 Kotlin 扩展函数是怎么实现的?其实也不难,前后不过 20 行代码:
这是入口函数,它接收一个初始值,还有一个 Lambda 表达式。注释写的很详细,我就不多解释了:
1  | kotlin复制代码/**  | 
这是整个 ktxSpan 的核心代码:
1  | kotlin复制代码/**  | 
这里用扩展函数封装了各种 Span 的 Api:
1  | kotlin复制代码/**  | 
各位小伙伴可以去下载 Demo 调试运行一下: github.com/chaxiu/Kotl…,欢迎 Star Fork。
思考题1:
这个 ktxSpan 还有优化的空间,你知道该怎么优化吗?
思考题2:
我们在前面高阶函数里写的 HTML DSL,是否也能用扩展来优化?
思考题3:
Kotlin 顶层扩展解决了 Java 的哪些问题?
思考题4:
Kotlin 类内扩展有哪些实际使用场景?
9. 结尾
- Kotlin 
顶层扩展解决了 Java 各种 Utils 的问题,它不仅提高了代码的可读性,还增强了易用性 - 可读性:
response.isNullOrBlank()比TextUtils.isEmpty(response)可读性更好 - 易用性:当我们在 IDE 里输入:
response.IDE 就会提示我们response.isNullOrBlank(),而 TextUtils 则无法自动提示。 
都看到这了,给点个赞呗!
回目录–>【Kotlin Jetpack 实战】
本文转载自: 掘金