Kotlin高阶函数实战(1)

本文详细探讨了Kotlin中Lambda表达式的不同写法,高阶函数的概念,以及如何使用HTMLKotlinDSL实现类型安全的HTML构建。通过实例展示了Lambda的简洁性和Kotlin语言的特性,如类型推导和带接收者函数的使用。
摘要由CSDN通过智能技术生成
2-3-1 第1种写法

这是原始代码,它的本质是用 object 关键字定义了一个匿名内部类

image.setOnClickListener(object: View.OnClickListener {

override fun onClick(v: View?) {

gotoPreview(v)

}

})

2-3-2 第2种写法

如果我们删掉 object 关键字,它就是 Lambda 表达式了,因此它里面 override 的方法也要跟着删掉:

image.setOnClickListener(View.OnClickListener { view: View? ->

gotoPreview(view)

})

上面的 View.OnClickListener 被称为: SAM Constructor—— SAM 构造器,它是编译器为我们生成的。Kotlin 允许我们通过这种方式来定义 Lambda 表达式。

思考题:

这时候,View.OnClickListener {} 在语义上是 Lambda 表达式,但在语法层面还是匿名内部类。这句话对不对?

2-3-3 第3种写法

由于 Kotlin 的 Lambda 表达式是不需要 SAM Constructor的,所以它也可以被删掉。

image.setOnClickListener({ view: View? ->

gotoPreview(view)

})

2-3-4 第4种写法

由于 Kotlin 支持类型推导,所以 View? 可以被删掉:

image.setOnClickListener({ view ->

gotoPreview(view)

})

2-3-5 第5种写法

当 Kotlin Lambda 表达式只有一个参数的时候,它可以被写成 it

image.setOnClickListener({ it ->

gotoPreview(it)

})

2-3-6 第6种写法

Kotlin Lambda 的 it 是可以被省略的:

image.setOnClickListener({

gotoPreview(it)

})

2-3-7 第7种写法

当 Kotlin Lambda 作为函数的最后一个参数时,Lambda 可以被挪到外面:

image.setOnClickListener() {

gotoPreview(it)

}

2-3-8 第8种写法

当 Kotlin 只有一个 Lambda 作为函数参数时,() 可以被省略:

image.setOnClickListener {

gotoPreview(it)

}

按照这个流程,在 IDE 里多写几遍,你自然就会理解了。一定要写,看文章是记不住的。

2-4 函数类型,高阶函数,Lambda表达式三者之间的关系
  • 将函数的参数类型返回值类型抽象出来后,就得到了函数类型(View) -> Unit 就代表了参数类型是 View 返回值类型为 Unit 的函数类型。

  • 如果一个函数的参数或者返回值的类型是函数类型,那这个函数就是高阶函数。很明显,我们刚刚就写了一个高阶函数,只是它比较简单而已。

  • Lambda 就是函数的一种简写

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

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

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 来实现的。

一张图总结:

思考题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:

<img src=“https://user-gold-cdn.xitu.io/2020/6/15/172b55ce7bf25419?imageslim” alt=“Kotlin Jetpack In Action” /img>

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, "$indent ")

}

builder.append(“ i n d e n t < / indent</ 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 标签

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 代码展示

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 {

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

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

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

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后我想说

为什么很多程序员做不了架构师?
1、良好健康的职业规划很重要,但大多数人都忽略了
2、学习的习惯很重要,持之以恒才是正解。
3、编程思维没能提升一个台阶,局限在了编码,业务,没考虑过选型、扩展
4、身边没有好的架构师引导、培养。所处的圈子对程序员的成长影响巨大。

金九银十面试季,跳槽季,整理面试题已经成了我多年的习惯!在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

BmxX-1712287928320)]

[外链图片转存中…(img-FPsDfFrP-1712287928320)]

[外链图片转存中…(img-zYsu8usP-1712287928321)]

[外链图片转存中…(img-FJN4FJGz-1712287928321)]

[外链图片转存中…(img-sXBHHNte-1712287928322)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后我想说

为什么很多程序员做不了架构师?
1、良好健康的职业规划很重要,但大多数人都忽略了
2、学习的习惯很重要,持之以恒才是正解。
3、编程思维没能提升一个台阶,局限在了编码,业务,没考虑过选型、扩展
4、身边没有好的架构师引导、培养。所处的圈子对程序员的成长影响巨大。

金九银十面试季,跳槽季,整理面试题已经成了我多年的习惯!在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-vaGe26iB-1712287928322)]

里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值