一张图看懂:函数类型高阶函数Lambda表达式三者之间的关系:

[图片上传中…(image-35e85a-1605078254997-2)]

回过头再看官方文档提供的例子:

fun <T, R> Collection.fold(
initial: R,
combine: (acc: R, nextElement: T) -> R
): R {
var accumulator: R = initial
for (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.name
website.text = user.blog
image.setOnClickListener { gotoImagePreviewActivity(user) }
}
复制代码

修改后:

user?.apply {

username.text = name
website.text = blog
image.setOnClickListener { gotoImagePreviewActivity(this) }
}
复制代码

请问:这个 apply 方法应该怎么实现?

上面的写法其实是简化后的 Lambda 表达式,让我们来反推,看看它简化前是什么样的:

// apply 肯定是个函数,所以有 (),只是被省略了
user?.apply() {

}

// Lambda 肯定是在 () 里面
user?.apply({ … })

// 由于 gotoImagePreviewActivity(this) 里的 this 代表了 user
// 所以 user 应该是 apply 函数的一个参数,而且参数名为:this
user?.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.name
website.text = self.blog
image.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.name
website.text = blog
image.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.name
website.text = this.blog
image.setOnClickListener { gotoImagePreviewActivity(this) }
}
复制代码

现在,关键来了。上面的 apply 方法是不是看起来就像是在 User 里增加了一个成员方法 apply()?

class User() {
val name: String = “”
val blog: String = “”

fun apply() {
// 成员方法可以通过 this 访问成员变量
username.text = this.name
website.text = this.blog
image.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)
复制代码

以上代码输出的内容是这样的:

Kotlin Jetpack In Action

Kotlin Jetpack In Action

-----------------------------------------

A super-simple project demonstrating how to use Kotlin and Jetpack step by step.

-----------------------------------------

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.

We will rewrite it from "Java + MVC" to "Kotlin + Coroutines + Jetpack + Clean MVVM", line by line, commit by commit.

-----------------------------------------

ScreenShot:

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: Kotlin Jetpack In Action
    */
    open class BaseElement(val name: String, val content: String = “”) : Element {
    // 每个节点,都会有很多子节点
    val children = ArrayList()
    // 存放节点参数:,里面的 src,alt
    val hashMap = HashMap<String, String>()

/**

  • 拼接 Html: Kotlin Jetpack In Action
    */
    override fun render(builder: StringBuilder, indent: String): String {
    builder.append(“ i n d e n t < indent< indent<name>\n”)
    if (content.isNotBlank()) {
    builder.append(" i n d e n t indent indentcontent\n")
    }
    children.forEach {
    it.render(builder, “KaTeX parse error: Expected 'EOF', got '}' at position 12: indent ") }̲ builder.append…indent</$name>\n”)
    return builder.toString()
    }
    }
    复制代码
4-2-3 定义各个子节点:

// 这是 HTML 最外层的标签:
class HTML : BaseElement(“html”) {
fun head(block: Head.() -> Unit): Head {
val head = Head()
head.block()
this.children += head
return head
}

fun body(block: Body.() -> Unit): Body {
val body = Body()
body.block()
this.children += body
return body
}
}

// 接着是 标签
class Head : BaseElement(“head”) {
fun title(block: () -> String): Title {
val content = block()
val title = Title(content)
this.children += title
return title
}
}

// 这是 Head 里面的 title 标签 <br/> class Title(content: String) : BaseElement(“title”, content)

// 然后是 标签
class Body : BaseElement(“body”) {
fun h1(block: () -> String): H1 {
val content = block()
val h1 = H1(content)
this.children += h1
return h1
}

fun p(block: () -> String): P {
val content = block()
val p = P(content)
this.children += p
return p
}

fun img(src: String, alt: String): IMG {
val img = IMG().apply {
this.src = src
this.alt = alt
}

this.children += img
return img
}
}

// 剩下的都是 body 里面的标签
class P(content: String) : BaseElement(“p”, content)
class H1(content: String) : BaseElement(“h1”, content)

class IMG : BaseElement(“img”) {
var src: String
get() = hashMap[“src”]!!
set(value) {
hashMap[“src”] = value
}

var alt: String
get() = hashMap[“alt”]!!
set(value) {
hashMap[“alt”] = value
}

// 拼接 标签
override fun render(builder: StringBuilder, indent: String): String {
builder.append(“ i n d e n t < indent< indent<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(" KaTeX parse error: Expected group as argument to '\"' at end of input: attr=\"value"")
}
return builder.toString()
}
}

复制代码

4-2-4 定义输出 HTML 代码的方法

fun html(block: HTML.() -> Unit): HTML {
val html = HTML()
html.block()
return html
}
复制代码

4-3 HTML 代码展示

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

学习福利

【Android 详细知识点思维脑图(技能树)】

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

详细整理在GitHub可以见;

Android架构视频+BAT面试专题PDF+学习笔记​

去面试时先看看自己复习到了哪个阶段就好。

虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

[外链图片转存中…(img-bs8geXlB-1710687781690)]

详细整理在GitHub可以见;

Android架构视频+BAT面试专题PDF+学习笔记​

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐