目录

一 标准函数

1.1 作用域函数

1.1.1 let

1.1.2  with

1.1.3 run

1.1.4 apply

1.1.5 also

1.1.6 takeIf 与 takeUnless

1.2 小结

二 扩展函数

2.1 扩展函数基本使用

2.2 运算符重载

2.3 最佳实践:扩展函数和运算符重载的合体

三  Kotlin高阶函数

3.1 基本定义

3.2 三种用法

3.2.1 双冒号 ::method

3.2.2 匿名函数

3.2.3 Lambda 表达式(常用)

四 内联函数inline

4.1 noinline

4.2 crossinline


一 标准函数

Kotlin 的标准函数指的是 Standara.kt 文件中定义的函数,任何Kotlin 代码都可以自由调用所有的标准函数。

1.1 作用域函数

Kotlin 标准库中有一些函数,它们的唯一目的是在对象的上下文中执行代码块。当对一个对象调用这样的函数并提供一个 Lambda表达式时,它会形成一个临时作用域。在此作用域中,可以访问该对象而无需其名称。这些函数称为作用域函数。共有以下五种:letrunwithapply 以及 also

1.1.1 let

函数式 { 参数 : 参数类型 -> 函数体 } ,它的参数就是传入本体,我们可以在函数体内对本体做任何事情。

fun doStudy(study: Study?){study?.let {it.doHomework()it.readBooks()}
}

1.1.2  with

with 函数接收两个参数,第一个参数可以是一个任意类型的对象,第二个参数是Lambda表达式。with 函数会在Lambda 表达式中提供第一个参数对象的上下文,并使用Lambda 表达式的最后一行代码作为返回值返回。

语法:

with(obj){// 这里是 ojb 的上下文"value" // with 函数的返回值}

例子,例如创建一个水果列表,将水果列表全部打印出来:

fun printFruits(){val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")val buffer = StringBuilder()buffer.append("Start eating fruits. \n")for (fruit in list) {buffer.append(fruit).append("\n")}buffer.append("Ate all fruits.")val result = buffer.toString()println(result)}

观察上面的代码我们可以看到多次调用了builder 对象的方法,其实这个时候就可以使用with 函数来让代码变得更加简单:

fun printFruits(){val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")val result = with(StringBuilder()) {append("Start eating fruits. \n")for (fruit in list) {append(fruit).append("\n")}append("Ate all fruits.")toString()}println(result)
}

在第一个参数传入了什么对象,那么Lambda 表达式内就会拥有这个对象的所有变量和函数,就相当于在对象内部调用函数,所以我们直接调用了StringBuilder 对象的append 函数。

1.1.3 run

run函数和with函数几乎一模一样,with 函数是内置函数形式调用with(obj){} ,run 函数是 obj.run{} ,一个是通过传入对象,一个是通过对象调用,作用相同,也是Lambda表达式内包含上下文环境,最后一句代码为返回值,run 函数只有一个参数就是Lambda 表达式。

fun printFruits(){val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")val result = StringBuilder().run {append("Start eating fruits. \n")for (fruit in list) {append(fruit).append("\n")}append("Ate all fruits.")toString()}println(result)
}

1.1.4 apply

apply 函数和 run 函数基本相同,不同的地方在于,apply 会返回对象本身,Lambda 表达式内不存在返回值,也是在Lambda 表达式中提供对象的上下文,结构为 obj.apply{}。

fun printFruits(){val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")val result = StringBuilder().apply {append("Start eating fruits. \n")for (fruit in list) {append(fruit).append("\n")}append("Ate all fruits.")}println(result.toString())
}

1.1.5 also

let 及 also 将上下文对象作为 lambda 表达式参数。如果没有指定参数名,对象可以用隐式默认名称 it 访问。

fun getRandomInt(): Int {return Random.nextInt(100).also {writeToLog("getRandomInt() generated value $it")}
}val i = getRandomInt()

1.1.6 takeIf 与 takeUnless

除了作用域函数外,标准库还包含函数 takeIf 及 takeUnless。这俩函数使你可以将对象状态检查嵌入到调用链中。

当以提供的谓词在对象上进行调用时,若该对象与谓词匹配,则 takeIf 返回此对象。否则返回 null。因此,takeIf 是单个对象的过滤函数。反之,takeUnless如果不匹配谓词,则返回对象,如果匹配则返回 null。该对象作为 lambda 表达式参数(it)来访问。

val number = Random.nextInt(100)val evenOrNull = number.takeIf { it % 2 == 0 }
val oddOrNull = number.takeUnless { it % 2 == 0 }
println("even: $evenOrNull, odd: $oddOrNull")

当在 takeIf 及 takeUnless 之后链式调用其他函数,不要忘记执行空检查或安全调用(?.),因为他们的返回值是可为空的。

val str = "Hello"
val caps = str.takeIf { it.isNotEmpty() }?.toUpperCase()
//val caps = str.takeIf { it.isNotEmpty() }.toUpperCase() // 编译错误
println(caps)

takeIf 及 takeUnless 与作用域函数一起特别有用。 一个很好的例子是用 let 链接它们,以便在与给定谓词匹配的对象上运行代码块。 为此,请在对象上调用 takeIf,然后通过安全调用(?.)调用 let。对于与谓词不匹配的对象,takeIf 返回 null,并且不调用 let

fun displaySubstringPosition(input: String, sub: String) {input.indexOf(sub).takeIf { it >= 0 }?.let {println("The substring $sub is found in $input.")println("Its start position is $it.")}
}displaySubstringPosition("010000011", "11")
displaySubstringPosition("010000011", "12")

1.2 小结

函数选择:为了帮助你选择合适的作用域函数,我们提供了它们之间的主要区别表。

函数 对象引用 返回值 是否是扩展函数
let it Lambda 表达式结果
run this Lambda 表达式结果
run - Lambda 表达式结果 不是:调用无需上下文对象
with this Lambda 表达式结果 不是:把上下文对象当做参数
apply this 上下文对象
also it 上下文对象

以下是根据预期目的选择作用域函数的简短指南:

  • 对一个非空(non-null)对象执行 lambda 表达式:let
  • 将表达式作为变量引入为局部作用域中:let
  • 对象配置:apply
  • 对象配置并且计算结果:run
  • 在需要表达式的地方运行语句:非扩展的 run
  • 附加效果:also
  • 一个对象的一组函数调用:with

apply 及 also 返回上下文对象。letrun 及 with 返回 lambda 表达式结果。


二 扩展函数

2.1 扩展函数基本使用

Kotlin 能够扩展一个类的新功能而无需继承该类或者使用像装饰者这样的设计模式。 这通过叫做 扩展 的特殊声明完成。 例如,你可以为一个你不能修改的、来自第三方库中的类编写一个新的函数。 这个新增的函数就像那个原始类本来就有的函数一样,可以用普通的方法调用。 这种机制称为扩展函数 。此外,也有扩展属性 , 允许你为一个已经存在的类添加新的属性。

建议向哪个类中添加扩展函数,就定义一个同名的Kotlin文件,便于以后查找。当然扩展函数可以定义在任何一个现有类中,并不一定非要创建新文件。不过通常而言,最好定义成顶层方法,这样可以让扩展函数拥有全局的访问域。

语法结构

fun ClassName.methodName(param1: Int, param2: Int): Int {//相关逻辑return 0
}

声明一个扩展函数,我们需要用一个 接收者类型 也就是被扩展的类型来作为他的前缀。 下面代码为 MutableList<Int> 添加一个swap 函数:

fun MutableList<Int>.swap(index1: Int, index2: Int) {val tmp = this[index1] // “this”对应该列表this[index1] = this[index2]this[index2] = tmp
}

现在,我们可以对任意 MutableList<Int> 调用该函数了:

val list = mutableListOf(1, 2, 3)
list.swap(0, 2) // “swap()”内部的“this”会保存“list”的值

当然,这个函数对任何 MutableList<T> 起作用,我们可以泛化它:

fun <T> MutableList<T>.swap(index1: Int, index2: Int) {val tmp = this[index1] // “this”对应该列表this[index1] = this[index2]this[index2] = tmp
}

注意:

  • 扩展函数在不修改某个类源码的情况下,动态地添加新的函数.className.
  • 扩展函数不能访问原有类的私有属性
  • 底层实际上是用写了个静态函数来实现的,将类的实例传入这个静态函数,然后搞一些操作

2.2 运算符重载

概念:同一运算符在不同的环境所表现的效果不同,如”+“在两个Int值之间表示两者的数值相加,在两个字符串之间表示,将字符串拼接,同时kotlin允许我们将任意两个类型的对象进行”+“运算,或者其他运算符操作。

语法结构:如下,其中operator 为运算符重载的关键字

class A {operator fun plus(a: A): A {//相关逻辑}
}

常见的语法糖表达式和实际调用函数对照表:

表达式 函数名
a * b a.times(b)
a / b a.div(b)
a % b a.rem(b)
a + b a.plus(b)
a - b a.minus(b)
a++ a.inc()
a-- a.dec()
!a a.not()
a == b a.equals(b)
”a > b“、”a < b“、”a >= b“、”a >= b“ a.compareTo(b)
a..b a.rangeTo(b)
a[b] a.get(b)
a[b] = c a.set(b, c)
a in b b.contains(a)

2.3 最佳实践:扩展函数和运算符重载的合体

operator fun ClassName.plus(param1: ClassName): ClassName {//相关逻辑return result
}

举例:

fun main() {"hello " * 6
}
operator fun String.times(int:Int){val builder = StringBuilder()repeat(int){builder.append(this)}println(builder.toString())
}

三  Kotlin高阶函数

3.1 基本定义

函数类型定义:

(String, Int) -> Unit

高阶函数定义:参数有函数类型或者返回值是函数类型的函数。

fun a(funParam: (Int) -> String): String {return funParam(1)
}

要传一个函数类型的参数,或者把一个函数类型的对象赋值给变量有三种方法

fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {val result = operation(num1, num2)return result
}

3.2 三种用法

3.2.1 双冒号 ::method

在 Kotlin 里,一个函数名的左边加上双冒号,它就不表示这个函数本身了,而表示一个对象,或者说一个指向对象的引用,但,这个对象可不是函数本身,而是一个和这个函数具有相同功能的对象。下面的例子表示将plus()函数作为参数传递给num1AndNum2()函数。

fun plus(num1: Int, num2: Int) : Int {return num1 + num2
}
val a = num1AndNum2(10, 8, ::plus)

3.2.2 匿名函数

val a = num1AndNum2(10, 8, fun(num1: Int, num2: Int) = num1 + num2)

3.2.3 Lambda 表达式(常用)

val a = num1AndNum2(10, 8) {n1, n2 -> n1 + n2
} 

Kotlin高阶函数的实现原理是将Lambda表达式在底层转为如上第二种匿名类的实现方式。每调用一次Lambda都会创建一个新的匿名类实例,会造成额外的内存和性能开销。由此Kotlin提供了内联函数功能。


四 内联函数inline

Kotlin编译器会将内联函数中的代码在编译时自动替换到调用它的地方,这样就不存在运行时的开销了。一般会把高阶函数声明为内联函数,即在定义高阶函数时加上inline关键字声明,这是一种良好的编程习惯,绝大多数高阶函数是可以直接声明成内联函数的。

inline fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {val result = operation(num1, num2)return result
}

4.1 noinline

如果一个内联高阶函数中含有多个函数类型参数,其中有一个函数类型参数不想内联,可在该参数前加上noinline关键字。

inline fun inlineFun(block1:() -> Unit, noinline block2: () -> Unit) {
}

为什么Kotlin还提供一个noline关键字来排除内联功能?

因为内联的函数类型参数在编译的时候会被代码替换,因此它没有真正的参数属性。非内联的函数类型参数可以自由地传递给其他函数,因为它就是一个真实的参数,而内联的参数类型只允许传递给另外一个内联函数,这是它最大的局限性。

内联函数和非内联函数的另一个区别是内联函数所引用的Lambda表达式中可以使用return关键字进行函数返回,而非内联函数只能局部返回。(注意,这个有点拗口,因为在Java8语言中,lambda表达式是可以明确使用return关键字的,而Kotlin语言中,lambda表达式却不能使用return关键字。而上面内联函数所引用的Lambda表达式中可以使用return关键字进行的函数返回是表示退出该lambda函数逻辑,局部返回,不再执行Lambda表示式的剩余内容。)

4.2 crossinline

如果在内联高阶函数中创建了另外的Lambda或者匿名类的实现,并且在这些实现中调用函数的参数,就会提示错误。如

inline fun runRunnable(block: () -> Unit) {val runnable = Runnable {block() // 提示错误}
}

因为内联函数所引用的Lambda表达式允许使用return进行函数返回,而高阶函数的匿名类实现中不允许使用return造成了冲突。

为什么高阶函数的匿名类实现中不允许使用return?匿名类中调用的函数类型参数是不可能进行外层调用函数返回的(匿名类啊,你连名字都没有你怎么在外部调用?),最多只能对匿名类中的函数调用进行返回。所以高阶函数的匿名类实现中不允许使用return。

crossinline用于保证内联函数Lambda表达式中一定不会使用return进行函数返回。

inline fun runRunnable(crossinline block: () -> Unit) {val runnable = Runnable {block() }
}

声明后runRunnable函数的Lambda中无法使用return进行函数返回了,仍然可以使用return@runRunnable进行局部返回

reified 关键字
reified 配合 inline可以将泛型实例化

inline fun <reified T> startActivity(context: Context, block: Intent.() -> Unit) {val intent = Intent(context, T::class.java)intent.block()context.startActivity(intent)
}

注:本文主要例子和参考

郭霖《第一行代码》 Kotlin部分

Kotlin官网 基本语法 - Kotlin 语言中文站

Kotlin系列四:标准函数、扩展函数、高阶函数、内联函数相关推荐

  1. 带默认参数值的函数 内联函数

    内联函数: 如果在声明函数时使用关键字inline,编译时会在调用处直接用函数体进行替换,而不是调用函数 既然是直接替换,那么里面肯定不能有循环语句,switch语句等 注:①内联函数可以避免函数调用 ...

  2. C++编程思想 第1卷 第9章 内联函数 内联函数 访问器和修改器

    一些人进一步访问函数的概念分为访问器 accessor,用于从一个对象读状态 信息和 修改器 mutator 用于修改状态信息. 而且,可以用重载函数为访问器 和修改器提供相同函数名,调用函数的方式决 ...

  3. kotlin学习笔记——内联函数

    在Kotlin中,使用inline修饰符标记内联函数. 与普通函数不同,内联函数在编译时被替换掉,不是真正的函数调用. 如kotlin提供的with函数,如下: @kotlin.internal.In ...

  4. 复习笔记(四)——C++内联函数

    什么是内联函数 内联函数是指通过 inline 关键字定义的函数,包括全局函数和类的成员函数. 内联函数在编译阶段展开,编译器将整个函数体代码嵌入到调用处,不产生函数跳转. 内联函数的定义 inlin ...

  5. 内联函数什么时候展开_内联函数

    内联函数什么时候展开 You know all of those Util files you create with all sorts of small functions that you en ...

  6. C++ 内联函数详解(搞清内联的本质及用法)

    目录 一.什么是内联函数 1.直观上定义: 2.更深入的思考: 二.为什么使用内联函数 1.为什么要代替部分宏定义 2.普通函数频繁调用的过程消耗栈空间 3.更深入的思考 三.内联函数和编译过程的相爱 ...

  7. c++ 函数 -函数重载 -特殊用途的语言特性(默认实参,内联函数 ,constexpr ,assert,NDEBUG)

    c++ 函数 -函数重载 -特殊用途的语言特性 文章目录 c++ 函数 -函数重载 -特殊用途的语言特性 函数重载 定义: 重载和const形参 const_cast 和重载 const_cast 重 ...

  8. C++ : 内联函数和引用变量

    一.内联函数 内联函数和普通函数的使用方法没有本质区别,我们来看一个例子,下面展示了内联函数的使用方法: #include <iostream> using namespace std; ...

  9. C++ —— C++内联函数

    C++内联函数 一.内联函数 1.C++中的const常量可以替代宏常数定义,如: const int A = 3;   #define A 3 C++中是否有解决方案替代宏代码片段呢?(替代宏代码片 ...

  10. c++之头文件基本输出,输入,名字空间,引用,内联函数,函数重载,缺省参数

    c++之头文件,基本输出,输入,名字空间,引用,内联函数,函数重载,缺省参数 文章目录 c++之头文件,基本输出,输入,名字空间,引用,内联函数,函数重载,缺省参数 前言 一.头文件,基本输出,输入 ...

最新文章

  1. java 嵌套对象转xml_Gson对Java嵌套对象和JSON字符串之间的转换 | 学步园
  2. Oracle中快速查找锁与锁等待
  3. DHCP Client 无法启动 拒绝访问
  4. C#—使用InstallerProjects打包桌面应用程序
  5. 计算机基础算术加法,2011年自考计算机基础知识精选部分及答案(一)-2
  6. Qt Creator设置场景环境
  7. 05.elasticsearch-index相关总结
  8. 绕固定轴分解_3轴 / 5轴 / 3+2到底是什么......??
  9. 高程 第7章函数表达式 7.1递归 7.2闭包
  10. 【数电】(二) 基本逻辑运算与逻辑门电路
  11. 小米路由器安装MT工具
  12. 农历日期用html怎么显示,怎么显示阴历(农历)日期的js代码?
  13. 怎么压缩word文档的大小?
  14. dsolve函数的功能_MATLAB 求解常微分方程的函数是dsolve()。
  15. 2020时尚COSMO美容大奖“破圈直播间”完美收官!打造美妆直播全新形式!
  16. C# 反射之Activator用法举例
  17. 7-2 jmu-python-汇率兑换
  18. android 电话管理
  19. 浏览器打开时总是hao123.com的网站解决办法
  20. 教你找回直接打开outlook附件文件编辑后保存但未另存为的附件文件?

热门文章

  1. Hls.js播放m3u8视频 DPlayer视频播放器(easypan) MSE简介
  2. 阿里云属于paas、iaas还是saas?
  3. msa2000映射到服务器,MSA2000命令行配置信息
  4. 利用GDAL(python)读取Landsat8数据
  5. 与“三节课”一同寻找“可能不存在的”规律
  6. 一篇文章,帮你搞定“查看更多”的全场景攻略
  7. 小程序-首页小列表查看更多
  8. 1.17-1.28中山纪念中学培训总结
  9. 硅谷裁员狂潮来袭,是泡沫破灭,还是涅槃重生?
  10. 庆科EMW3080学习之旅① 搭建开发环境,开始一个 “hellow world” 串口打印。