Kotlin学习(四):kotlin函数的高级应用,阿里内部Android应届生就业宝典

36 阅读7分钟

===============================================================================

1.内联函数概念


用inline关键字进行修饰,如果该函数接受一个匿名函数(lambda表达式),则可以使用inline关键字修饰改函数,然后接受的匿名函数会被编译器优化,内联到函数内部,会节省开销。

示例如下所示:

//内联函数

inline fun IntArray.forEatch(action:(Int)->Unit):Unit{

for(element in this) action(element)

}

//调用处

fun main(){

val ints = intArrayOf()

ints.forEach{

println("hello$it")

}

}

//实际编译之后的调用(大致)

fun main(){

val ints = intArrayOf()

for(element in this) {

println("hello$element")

}

}

上述所示,使用inline修饰之后,在编译期间kotlin会对代码调用栈进行优化。

2.高阶函数和内联函数


示例如下所示:

inline fun cost(block:()->Unit){

val start = System.currentTimeMillis()

block()

println(System.currentTimeMillis() - start)

}

//调用

cost{

println("hello")

}

//实际编译效果

val start = System.currentTimeMillis()

println("hello")

println(System.currentTimeMillis() - start)

高阶函数内联

  • 函数本身被内联到调用处

  • 函数的函数参数被内联到调用处

3.内联函数的限制以及详细说明


  • public/protected的内敛方法只能访问对应类的public成员(也可以理解,毕竟可以在编译期间优化到调用处,怎么访问私有属性呢)

  • 内联函数的内联函数参数不能被储存(赋值给变量)

  • 内联函数的内联函数参数只能传递给其他内联函数参数

另: 与inline一块配合使用的有noinline以及crossinline。关于内联函数的详细说明,以及这个三个参数的详细区别,可参考:kotlin:inline、noinline、crossinline还傻傻分不清楚?看这一篇就够了

三、kotlin几个常用的高阶函数let、run、also、apply、use

==========================================================================================================

1.let


let源码如下所示:

public inline fun <T, R> T.let(block: (T) -> R): R {

contract {

callsInPlace(block, InvocationKind.EXACTLY_ONCE)

}

return block(this)

}

注意:我们可以忽略掉contract契约检查(下面遇到的源码如包含contract,都可以进行忽略,且不在提及),那么核心的函数其实就是return block(this)

let : 将receiver作为参数传入block,并且返回block的返回值

简单使用实例如下所示:

val strLenght = arrayOf("hello", "world").let {

it.joinToString(" ") { s: String ->

"slength:s length: {s.length}"

}

}

println(strLenght)

//输出

hello length: 5 world length: 5

2.run


run源码如下所示:

public inline fun <T, R> T.run(block: T.() -> R): R {

contract {

callsInPlace(block, InvocationKind.EXACTLY_ONCE)

}

return block()

}

接收的block类型为T.() -> Unit 所以 block里面可使用this指代调用者

run : block里面可使用this指代调用者,并且返回block的返回值

简单使用实例如下所示:

val strLenght = arrayOf("hello", "world").run {

this.joinToString(" ") { s: String ->

"slength:s length: {s.length}"

}

}

println(strLenght)

//输出

hello length: 5 world length: 5

3.also


also源码如下所示:

public inline fun T.also(block: (T) -> Unit): T {

contract {

callsInPlace(block, InvocationKind.EXACTLY_ONCE)

}

block(this)

return this

}

和let的区别就是返回类型不一样了,block是无返回类型,整个函数返回调用者T

核心函数 block(this) return this

简单使用实例如下所示:

val arrayStr = arrayOf("hello", "world").also {

val joinToString = it.joinToString(" ") { s: String ->

"slength:s length: {s.length}"

}

println(joinToString)

}

println(arrayStr is Array)

//输出

hello length: 5 world length: 5

true

4.apply


apply源码如下所示:

public inline fun T.apply(block: T.() -> Unit): T {

contract {

callsInPlace(block, InvocationKind.EXACTLY_ONCE)

}

block()

return this

}

和run的区别就是返回类型不一样了,block是无返回类型,整个函数返回调用者T

核心函数 block() return this

简单使用实例如下所示:

val arrayStr = arrayOf("hello", "world").apply{

val joinToString = it.joinToString(" ") { s: String ->

"slength:s length: {s.length}"

}

println(joinToString)

}

println(arrayStr is Array)

//输出

hello length: 5 world length: 5

true

5.use


use源码如下所示:

public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {

contract {

callsInPlace(block, InvocationKind.EXACTLY_ONCE)

}

var exception: Throwable? = null

try {

return block(this)

} catch (e: Throwable) {

exception = e

throw e

} finally {

when {

apiVersionIsAtLeast(1, 1, 0) -> this.closeFinally(exception)

this == null -> {}

exception == null -> close()

else ->

try {

close()

} catch (closeException: Throwable) {

// cause.addSuppressed(closeException) // ignored here

}

}

}

}

核心代码就是try catch那一块,很明显,使用use函数自动帮我们处理了,异常以及资源的关闭。所以使用Closeable相关操作的时候,推荐使用use来进行资源管理

简单使用实例如下所示:

val datas = LinkedList<Pair<Char, Int>>()

File("build.gradle.kts").inputStream().reader().buffered().use {

var strLine = it.readLine()

while (strLine != "") {

strLine.groupBy { char ->

char

}.map { map ->

map.key to map.value.size

}.run {

datas.addAll(this)

}

strLine = it.readLine()

}

}

println(datas.joinToString())

//输出

(p, 1), (l, 1), (u, 1), (g, 1), (i, 1), (n, 1), (s, 1), ( , 1), ({, 1), (/, 2), ( , 5), (i, 1), (d, 1), (', 2), (j, 1), (a, 2), (v, 1), (/, 2), ( , 7), (i, 4), (d, 1), (', 4), (o, 3), (r, 3), (g, 1), (., 5), (j, 2), (e, 2), (t, 2), ...展示内容有限,就不全部粘贴过来了

四、kotlin集合序列变换forEach、filter、map、flatmap、sum、reduce、fold

===========================================================================================================================

1.forEach函数


forEach,熟悉java的朋友相信都不陌生,且本系列文章在之前也多次用到forEach,下面我们就看看forEach具体是怎么操作的吧

forEach源码如下所示:

public inline fun Array.forEach(action: (T) -> Unit): Unit {

for (element in this) action(element)

}

很简单是不是,就是对集合元素进行了循环处理,调用了传进来的block

那按照惯例,也提供一个小demo

arrayOf(1,2,3,4).forEach {

print(it)

}

//输出

1234

2.集合映射操作相关filter、map、flatMap


filter,保留满足条件的元素

源码如下所示:

public inline fun Array.filter(predicate: (T) -> Boolean): List {

return filterTo(ArrayList(), predicate)

}

public inline fun <T, C : MutableCollection> Array.filterTo(destination: C, predicate: (T) -> Boolean): C {

for (element in this) if (predicate(element)) destination.add(element)

return destination

}

正所谓,知其然也要知其所以然。如上源码所示,filter的本质就是,循环判断block是否满足条件,如果满足则添加进入list,最后返回

简单使用如下:

arrayOf(1,2,3,4,5,6).filter {

it>3

}.joinToString ().let {

println(it)

}

//输出

4, 5, 6

map,集合中所有的元素一一映射到其他的元素构成新的集合

源码如下所示:

public inline fun <T, R> Array.map(transform: (T) -> R): List {

return mapTo(ArrayList(size), transform)

}

public inline fun <T, R, C : MutableCollection> Array.mapTo(destination: C, transform: (T) -> R): C {

for (item in this)

destination.add(transform(item))

return destination

}

分析发现,其实本质就是创建一个新的集合,加入传入map的函数的返回值而已

简单使用如下:

arrayOf(1,2,3,4,5,6).map {

"$it hello"

}.joinToString().let {

println(it)

}

//输出

1 hello, 2 hello, 3 hello, 4 hello, 5 hello, 6 hello

flatMap,集合中所有的元素一一映射到新集合并合并这些集合得到新集合

源码如下所示:

public inline fun <T, R> Array.flatMap(transform: (T) -> Iterable): List {

return flatMapTo(ArrayList(), transform)

}

public inline fun <T, R, C : MutableCollection> Array.flatMapTo(destination: C, transform: (T) -> Iterable): C {

for (element in this) {

val list = transform(element)

destination.addAll(list)

}

return destination

}

好多同学可能都对flatMap所要表达的意思,感到困惑难以理解,但是我们仔细分析一波源码,弄懂原理就简单很多了。

首先可以看到flatMap也是创建了新的ArrayList集合并且将函数重新传入到了flatMapTo的这个函数里了,这个时候我们注意,我们传入的函数的返回类型必须是可迭代的(Iterable类型)。

那我们在看flatMapTo干了什么事情,先是循环调用者的集合,然后调用函数,拿到我们传入的函数返回的集合,最后在addAll到信创建的ArrayList返回。

这么一说是不是就好理解flatMap的映射关系了,下面我们可以看一个demo

Demo如下所示:

arrayOf(1,2,3,4,5,6).flatMap {

val arrayList = ArrayList()

arrayList.add(it)

arrayList.add(1111)

arrayList

}.joinToString().let {

println(it)

}

//输出

1, 1111, 2, 1111, 3, 1111, 4, 1111, 5, 1111, 6, 1111

3.集合的聚合操作说明sum、reduce、fold


sum,所有元素求和

源码如下所示:

public fun Array.sum(): Int {

var sum: Int = 0

for (element in this) {

sum += element

}

return sum

}

较为简单,就不做说明了。

Demo所示如下:

总结

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

我个人是做Android开发,已经有十来年了,目前在某创业公司任职CTO兼系统架构师。虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

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

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

详细整理在GitHub可以见;

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

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

最后,赠与大家一句话,共勉!