无论函数还是方法我们这里统称函数,Koltin中的函数要比Java中丰富的多,我们这篇文章来了解下Kotlin中的各类函数。
内联函数
Android开发中,打印信息一般我们会用到Log类,Log中每个方法我们都要传两个参数,第一个tag参数在Kotlin中我们可以像下面封装一下,这样就只需要传一个参数。
inline fun <reified T> T.debug(log:Any)
{
Log.d(T::class.simpleName, log.toString())
}
我们发现它可以通过泛型参数 T 来获取到T的具体类型,并且拿到它的类名——当然,如果你愿意,你甚至可以调用它的构造方法来构造一个对象出来——为什么 Kotlin 可以做到呢?因为这段代码是 inline 的,最终编译时是要编译到调用它的代码块中,这时候T的类型实际上是确定的,因而 Kotlin 通过 reified 这个关键字告诉编译器,T 这个参数可不只是个摆设,我要把它当实际类型来用。
在高阶函数前增加inline注解可以指定函数內联,inline 标记即影响函数本身也影响传递进来的 lambda 函数:所有的这些都将被关联到调用点。内联可能会引起生成代码增长,但我们可以合理的解决它(不要内联太大的函数)。也可以使用noinline来指定某些函数不进行內联。
inline fun foo(inlined: () -> Uint, @noinline notInlined: () -> Unit) {
//...
}
可以内联的 lambda 表达式只能在内联函数内部调用或者作为可内联的参数传递, 但是 noinline 的可以以任何我们喜欢的方式操作:存储在字段中、传送它等等。
单表达式函数
如果一个函数的函数体只有一个表达式,函数体可以直接写在 “=”之后,也就是这样:
fun double(x: Int): Int = x * 2
再例如下面这样:
fun eval(expr: Expr): Double = when(expr) {
is Expr.Const -> expr.number
is Expr.Sum -> eval(expr.e1) + eval(expr.e2)
Expr.NotANumber -> Double.NaN
// the `else` clause is not required because we've covered all the cases
}
这里使用了when关键字,类似于java中的switch,但比之更强大。
匿名函数
/**
* 匿名函数,没有名字,其他语法和常规函数类似
*
* 声明一个匿名函数,这里用表达式来表示函数体,也就是单表达式函数
*/
var test3= fun(x:Int,y:Int):Int=x+y
/**
* 声明一个匿名函数,这里用代码块来表示函数体
*/
var test4= fun(x:Int,y:Int):Int {
return x+y
}
/**
* 声明一个匿名函数,当返回值类型可以推断出,可以省略
*/
var test5= fun(x:Int,y:Int)=x+y
fun main(args: Array<String>) {
println(test3(1,2))
println(test4(1,2))
println(test5(1,2))
}
高阶函数与lambda表达式
高阶函数就是可以接受函数作为参数或返回一个函数的函数。比如 lock() 就是一个很好的例子
fun lock<T>(lock: Lock, body: () -> T ) : T {
lock.lock()
try {
return body()
}
finally {
lock.unlock()
}
}
在 kotlin 中并且许多类似语言都有一个约定,就是如果最后一个参数是函数,可以定义在括号外面:
val result = lock(lock, { sharedResource.operation() })
//等同于
lock (lock) {
sharedResource.operation()
}
Lambda表达式是定义匿名函数的简单形式,Lambda表达式与匿名函数有哪些区别:
- 是否指定返回值的类型
Lambda表达式返回值的类型通过以自动推断得到
匿名函数的返回值类型必须手动指定,如果未指定返回值类型,默认返回值类型为Unit
- return的行为
Lambda 表达式内的 return 将会从包含这个Lambda表达式的函数中返回
匿名函数内的 return 只会从匿名函数本身返
尾递归函数
什么是尾递归?尾递归就是递归调用的那行代码是函数的最后一行代码。Koltin中对尾递归进行了优化,使用tailrec修饰的函数称为尾递归函数,避免内存溢出的风险。
tailrec fun findFixPoint(x: Double = 1.0): Double
= if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))
这段代码计算的是数学上的余弦不动点。Math.cos 从 1.0 开始不断重复,直到值不变为止,结果是 0.7390851332151607 这段代码和下面的是等效的:
private fun findFixPoint(): Double {
var x = 1.0
while (true) {
val y = Math.cos(x)
if ( x == y ) return y
x = y
}
}
实际上就是用while迭代的方式代替了函数的递归调用。
局部函数
局部函数,就是定义在函数体的内函数。
fun dfs(graph: Graph) {
fun dfs(current: Vertex, visited: Set<Vertex>) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v, visited)
}
dfs(graph.vertices[0], HashSet())
}
局部函数可以访问外部函数中的局部变量, 因此, 在上面的例子中, visited 可以定义为一个局部变量。
静态方法
有两种方式
- companion object 伴生对象与类对应,一个类只有一个伴生对象
class Main2Activity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
}
companion object {
fun getStatic() {
}
fun getStaticString(string: String): String? {
return null
}
}
}
- object 使用object关键字声明一种特殊的类,这个类只有一个实例,因此看起来整个类就好像是一个对象一样。
object MoreImageUtils {
fun filesToMultipartBodyParts(files: List<File>): List<MultipartBody.Part>? {
return null;
}
}
这里把类声明时的class关键字改成了object,这个类里面的成员默认都是static的。
顶层函数
在Kotlin中,函数能够被定义为top_level,即包下的函数。函数的定义方式不同,其作用域也不相同。当然,函数的作用域还与修饰符相关。
// file name: example.kt
package foo
private fun foo() {} // 只在 example.kt 文件内可访问
internal val baz = 6 // 在同一个模块(module)内可以访问
fun testFun() {} //默认修饰符public,被其修饰的在任何位置都能访问
扩展函数
Kotlin的扩展函数功能使得我们可以为现有的类添加新的函数,实现某一具体功能 。
扩展函数是静态解析的,并未对原类添加函数或属性,对类本身没有任何影响。
fun Activity.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT){
Toast.makeText(this, message, duration)
.show()
}
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
Kotlin提供了一些扩展函数和顶层函数。
- let函数
let默认当前这个对象作为闭包的it参数,返回值是函数里面最后一行,或者指定return
fun testLet(): Int {
// fun <T, R> T.let(f: (T) -> R): R { f(this)}
"testLet".let {
println(it)
println(it)
println(it)
return 1
}
}
- apply函数
调用某对象的apply函数,在函数范围内,可以任意调用该对象的任意方法,并返回该对象。
ArrayList<String>().apply {
add("testApply")
add("testApply")
add("testApply")
println("this = " + this)
}.let { println(it) }
// 运行结果
// this = [testApply, testApply, testApply]
// [testApply, testApply, testApply]
- with函数
with函数返回是最后一行,然后可以直接调用对象的方法,感觉像是let和apply的结合。
fun testWith() {
// fun <T, R> with(receiver: T, f: T.() -> R): R = receiver.f()
with(ArrayList<String>()) {
add("testWith")
add("testWith")
add("testWith")
println("this = " + this)
}.let { println(it) }
}
// 运行结果
// this = [testWith, testWith, testWith]
// kotlin.Unit
- run
run函数和apply函数很像,只不过run函数是使用最后一行的返回,apply返回当前自己的对象。
fun testRun() {
// fun <T, R> T.run(f: T.() -> R): R = f()
"testRun".run {
println("this = " + this)
}.let { println(it) }
}
// 运行结果
// this = testRun
// kotlin.Unit
- repeat
没什么好讲的,重复做某件事情,他的源码如下:
@kotlin.internal.InlineOnly
public inline fun repeat(times: Int, action: (Int) -> Unit) {
for (index in 0..times - 1) {
action(index)
}
}
- lazy
lazy延迟运算,当第一次访问时,调用相应的初始化函数,例如:
fun readFromDb(): String = ...
val lazyString = lazy { readFromDb() }
val string = lazyString.value
当第一次使用 lazyString时, lazy 闭包会调用。一般用在单例模式。
- use
use 用在 Java 上的 try-with-resources 表达式上, 例如:
val input = Files.newInputStream(Paths.get("input.txt"))
val byte = input.use({ input.read() })
use 无论如何都会将 input close, 避免了写复杂的 try-catch-finally 代码。
最后的小例:
val long = 100L
val db2 = 100.db
//扩展属性
private val Int.db: BigDecimal
get() = BigDecimal(this)
data class Money(val amount:BigDecimal, val currency: String)
//运算符重载
operator fun Money.plus(money: Money) =
if (currency == money.currency)
{
Money(amount+money.amount, currency)
}else {
throw IllegalArgumentException("We're have a problem here!")
}
//任何具有单个参数的扩展函数都可以使用中缀符
infix fun Int.percentOf(money:Money) = money.amount.multiply(BigDecimal(this)).divide(BigDecimal(100))
fun main(args: Array<String>) {
val res = 7.percentOf(Money(3.db, "$"))
//中缀调用
7 percentOf Money(2.db, "$")
println(res)
val costs = Money(100.db, "$").plus(Money(200.db, "$"))
println(costs.amount)
}