冲呀!Kotlin-Jetpack- 实战之 Kotlin- 高阶函数!,android 初级面试题 2018
image.setOnClickListener() {gotoPreview(it)}复制代码
2-3-8 第 8 种写法
当 Kotlin 只有一个 Lambda 作为函数参数时,()
可以被省略:
image.setOnClickListener {gotoPreview(it)}复制代码
按照这个流程,在 IDE 里多写几遍,你自然就会理解了。一定要写,看文章是记不住的。
2-4 函数类型,高阶函数,Lambda 表达式三者之间的关系
将函数的
参数类型
和返回值类型
抽象出来后,就得到了函数类型
。(View) -> Unit
就代表了参数类型
是 View返回值类型
为 Unit 的函数类型。如果一个函数的参数
或者
返回值的类型是函数类型,那这个函数就是高阶函数
。很明显,我们刚刚就写了一个高阶函数,只是它比较简单而已。Lambda 就是函数的一种
简写
一张图看懂:函数类型
,高阶函数
,Lambda表达式
三者之间的关系:
[图片上传中...(image-35e85a-1605078254997-2)]
回过头再看官方文档提供的例子:
fun <T, R> Collection<T>.fold(initial: R,combine: (acc: R, nextElement: T) -> R): R {var accumulator: R = initialfor (element: T in this) {accumulator = combine(accumulator, element)}return accumulator}复制代码
看看这个函数类型:(acc: R, nextElement: T) -> R
,是不是瞬间就懂了呢?这个函数接收两个参数,第一个参数类型是R
,第二个参数是T
,函数的返回类型是R
。
3. 带接收者(Receiver)的函数类型:A.(B,C) -> D
说实话,这个名字也对初学者不太友好:带接收者的函数类型
(Function Types With Receiver),这里面的每一个字(单词)我都认识,但单凭这么点信息,初学者真的很难理解它的本质。
还是绕不开一个问题:为什么?
3-1 为什么要引入:带接收者的函数类型?
我们在上一章节中提到过,用 apply 来简化逻辑,我们是这样写的:
修改前:
if (user != null) {...username.text = user.namewebsite.text = user.blogimage.setOnClickListener { gotoImagePreviewActivity(user) }}复制代码
修改后:
user?.apply {...username.text = namewebsite.text = blogimage.setOnClickListener { gotoImagePreviewActivity(this) }}复制代码
请问:这个 apply 方法应该怎么实现?
上面的写法其实是简化后的 Lambda 表达式,让我们来反推,看看它简化前是什么样的:
// apply 肯定是个函数,所以有 (),只是被省略了 user?.apply() {...}
// Lambda 肯定是在 () 里面 user?.apply({ ... })
// 由于 gotoImagePreviewActivity(this) 里的 this 代表了 user// 所以 user 应该是 apply 函数的一个参数,而且参数名为:thisuser?.apply({ this: User -> ... })复制代码
所以,现在问题非常明确了,apply 其实接收一个 Lambda 表达式:{ this: User -> ... }
。让我们尝试来实现这个 apply 方法:
fun User.apply(block: (self: User) -> Unit): User{block(self)return this}
user?.apply { self: User ->...username.text = self.namewebsite.text = self.blogimage.setOnClickListener { gotoImagePreviewActivity(this) }}复制代码
由于 Kotlin 里面的函数形参是不允许被命名为 this
的,因此我这里用的 self
,我们自己写出来的 apply 仍然还要通过 self.name
这样的方式来访问成员变量,但 Kotlin 的语言设计者能做到这样:
// 改为 this// ↓fun User.apply(block: (this: User) -> Unit): User{// 这里还要传参数// ↓block(this)return this}
user?.apply { this: User ->...// this 可以省略// ↓username.text = this.namewebsite.text = blogimage.setOnClickListener { gotoImagePreviewActivity(this) }}复制代码
从上面的例子能看到,我们反推的 apply 实现比较繁琐,需要我们自己调用:block(this)
,因此 Kotlin 引入了带接收者的函数类型
,可以简化 apply 的定义:
// 带接收者的函数类型// ↓
fun User.apply(block: User.() -> Unit): User{// 不用再传 this// ↓block()return this}
user?.apply { this: User ->...username.text = this.namewebsite.text = this.blogimage.setOnClickListener { gotoImagePreviewActivity(this) }}复制代码
现在,关键来了。上面的 apply 方法是不是看起来就像是在 User 里增加了一个成员方法 apply()?
class User() {val name: String = ""val blog: String = ""
fun apply() {// 成员方法可以通过 this 访问成员变量 username.text = this.namewebsite.text = this.blogimage.setOnClickListener { gotoImagePreviewActivity(this) }}}复制代码
所以,从外表上看,带接收者的函数类型,就等价于成员方法。但从本质上讲,它仍是通过编译器注入 this 来实现的。
一张图总结:
[图片上传中...(image-b7c5e-1605078254996-1)]
思考题 2:
带接收者的函数类型,是否也能代表扩展函数?
思考题 3:
请问:A.(B,C) -> D
代表了一个什么样的函数?
4. HTML Kotlin DSL 实战
官方文档在高阶函数的章节里提到了:用高阶函数来实现 类型安全的 HTML 构建器。官方文档的例子比较复杂,让我们来写一个简化版的练练手吧。
4-1 效果展示:
val htmlContent = html {head {title { "Kotlin Jetpack In Action" }}body {h1 { "Kotlin Jetpack In Action"}p { "-----------------------------------------" }p { "A super-simple project demonstrating how to use Kotlin and Jetpack step by step." }p { "-----------------------------------------" }p { "I made this project as simple as possible," +" so that we can focus on how to use Kotlin and Jetpack" +" rather than understanding business logic." }p {"We will rewrite it from "Java + MVC" to" +" "Kotlin + Coroutines + Jetpack + Clean MVVM"," +" line by line, commit by commit."}p { "-----------------------------------------" }p { "ScreenShot:" }img(src = "https://user-gold-cdn.xitu.io/2020/6/15/172b55ce7bf25419?imageslim",alt = "Kotlin Jetpack In Action")}}.toString()
println(htmlContent)复制代码
以上代码输出的内容是这样的:
<html><head><title>Kotlin Jetpack In Action</title></head><body><h1>Kotlin Jetpack In Action</h1><p>
</p><p>A super-simple project demonstrating how to use Kotlin and Jetpack step by step.</p><p>
</p><p>I made this project as simple as possible, so that we can focus on how to use Kotlin and Jetpack rather than understanding business logic.</p><p>We will rewrite it from "Java + MVC" to "Kotlin + Coroutines + Jetpack + Clean MVVM", line by line, commit by commit.</p><p>
</p><p>ScreenShot:</p><img src="https://user-gold-cdn.xitu.io/2020/6/15/172b55ce7bf25419?imageslim" alt="Kotlin Jetpack In Action" /img></body></html>复制代码
4-2 HTML Kotlin DSL 实现
4-2-1 定义节点元素的接口
interface Element {// 每个节点都需要实现 render 方法 fun render(builder: StringBuilder, indent: String): String}复制代码
所有的 HTML 节点都要实现 Element 接口,并且在 render 方法里实现 HTML 代码的拼接:<title> Kotlin Jetpack In Action </title>
4-2-2 定义基础类
/**
每个节点都有 name,content: <title> Kotlin Jetpack In Action </title>*/open class BaseElement(val name: String, val content: String = "") : Element {// 每个节点,都会有很多子节点 val children = ArrayList<Element>()// 存放节点参数:<img src= "" alt=""/>,里面的 src,altval hashMap = HashMap<String, String>()
/**
拼接 Html: <title> Kotlin Jetpack In Action </title>*/override fun render(builder: StringBuilder, indent: String): String {builder.append("name>\n")if (co
ntent.isNotBlank()) {builder.append(" content\n")}children.forEach {it.render(builder, "indent ")}builder.append("indent</$name>\n")return builder.toString()}}复制代码
4-2-3 定义各个子节点:
// 这是 HTML 最外层的标签: <html>class HTML : BaseElement("html") {fun head(block: Head.() -> Unit): Head {val head = Head()head.block()this.children += headreturn head}
fun body(block: Body.() -> Unit): Body {val body = Body()body.block()this.children += bodyreturn body}}
// 接着是 <head> 标签 class Head : BaseElement("head") {fun title(block: () -> String): Title {val content = block()val title = Title(content)this.children += titlereturn title}}
// 这是 Head 里面的 title 标签 <title>class Title(content: String) : BaseElement("title", content)
// 然后是 <body> 标签 class Body : BaseElement("body") {fun h1(block: () -> String): H1 {val content = block()val h1 = H1(content)this.children += h1return h1}
fun p(block: () -> String): P {val content = block()val p = P(content)this.children += preturn p}
fun img(src: String, alt: String): IMG {val img = IMG().apply {this.src = srcthis.alt = alt}
this.children += imgreturn img}}
// 剩下的都是 body 里面的标签 class P(content: String) : BaseElement("p", content)class H1(content: String) : BaseElement("h1", content)
class IMG : BaseElement("img") {var src: Stringget() = hashMap["src"]!!set(value) {hashMap["src"] = value}
var alt: Stringget() = hashMap["alt"]!!set(value) {hashMap["alt"] = value}
// 拼接 <img> 标签 override fun render(builder: StringBuilder, indent: String): String {builder.append("name")builder.append(renderAttributes())builder.append(" /$name>\n")return builder.toString()}
private fun renderAttributes(): String {val builder = StringBuilder()for ((attr, value) in hashMap) {builder.append(" value"")}return builder.toString()}}
复制代码
4-2-4 定义输出 HTML 代码的方法
fun html(block: HTML.() -> Unit): HTML {val html = HTML()html.block()return html}复制代码
4-3 HTML 代码展示
class WebActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_web)
val myWebView: WebView = findViewById(R.id.webview)myWebView.loadDataWithBaseURL(null, getHtmlStr(), "text/html", "UTF-8", null);}
private fun getHtmlStr(): String {return html {head {title { "Kotlin Jetpack In Action" }}body {h1 { "Kotlin Jetpack In Action"}p { "-----------------------------------------" }p { "A super-simple project demonstrating how to use Kotlin and Jetpack step by step." }p { "-----------------------------------------" }p { "I made this project as simple as possible," +" so that we can focus on how to use Kotlin and Jetpack" +" rather than understanding business logic." }p {"We will rewrite it from "Java + MVC" to" +" "Kotlin + Coroutines + Jetpack + Clean MVVM"," +" line by line, commit by commit."}p { "-----------------------------------------" }p { "ScreenShot:" }img(src = "https://user-gold-cdn.xitu.io/2020/6/15/172b55ce7bf25419?imageslim", alt = "Kotlin Jetpack In Action")}}.toString()}}复制代码
4-4 展示效果:
以上修改的具体细节可以看我这个 GitHub Commit。
4-5 HTML DSL 的优势
类型安全
支持自动补全
支持错误提示
节省代码量,提高开发效率
评论