高阶函数
一个函数可以将另一个函数当作参数。将其他函数用作参数的函数称为“高阶函数”。
当接口中只有一个函数时使用高阶函数可省略接口,比如项目中常见的接口回调:
接口方式:
private var clickListener :ClickListener ?=null
fun setClickListener(clickListener :ClickListener){
this.clickListener=clickListener
}
interface ClickListener{
fun click(url:String)
}
View.setOnClickListener {
clickListener?.click(bean?.url?:"")
}
调用:
goodsAdapter?.setClickListener(object :BottomGoodsAdapter.ClickListener {
override fun click(url: String) {
dismiss()
MyApplication.getBus().post("start_floatingView:$url")
}
})
kotlin高阶函数方式实现:
lateinit var mListener: (String) -> Unit
fun setClickListener(listener: (String) -> Unit) {
this.mListener = listener
}
View.setOnClickListener {
mListener(bean?.url?:"")
}
调用:
goodsAdapter?.setClickListener {
dismiss()
MyApplication.getBus().post("start_floatingView:$it")
}
可以看出使用kotlin高阶函数后,一没有了接口定义,二是没有了匿名内部类,代码更简洁。
内联函数
我们的项目里常常会创建一些 Util 类,用于分类整理那些会在许多地方用到的小型函数 ,如果这类函数接收了另一个函数作为参数(即高阶函数),则可能会造成一些额外的对象分配,通过使用 inline关键字,您可以避免这种情况并提升应用性能。
1.函数调用——工作原理
internal object Utils {
@JvmStatic
fun calculate(a: Int, b: Int, cal: (Int, Int) -> Int): Int {
return cal(a, b)
}
@JvmStatic
fun main(args: Array<String>) {
calculate(6, 3,{ a, b -> a+b })
calculate(4, 5){ a, b ->
println("$a * $b = ${a * b}")
a * b
}
}
}
这样其实是看不出什么问题的,Kotlin 文件编译后会生成对应的 class 文件,所以我们将 class 文件反编译成 Java 文件后再看。如果使用Android Studio或者IntelliJ IDEA,可以按照如下方式查看 Kotlin 文件对应反编译后的 Java 文件:
打开目标 Kotlin 文件 查看 Kotlin 文件字节码:Tools –> Kotlin –> Show Kotlin ByteCode 在 kotlin 文件字节码页面中点击左上角的 decompile 按钮,就会生成对应的 Java 文件
可以看出,编译器创建了两个Lambda的实例,并进行了两次calculate函数调用。每个高阶函数都会造成函数对象的创建和内存的分配,从而带来额外的运行时开销。
2.内联函数——工作原理
为了提升我们应用的性能,我们可以通过使用 inline 关键字,来减少函数对象的创建:
internal object Utils {
@JvmStatic
inline fun calculate(a: Int, b: Int, cal: (Int, Int) -> Int): Int {
return cal(a, b)
}
@JvmStatic
fun main(args: Array<String>) {
calculate(6, 3,{ a, b -> a+b })
calculate(4, 5){ a, b ->
println("$a * $b = ${a * b}")
a * b
}
}
}
我们再看最终的 Java 文件:
由于使用了 inline 关键字,编译器会将内联函数的内容复制到调用处,从而避免了创建新的函数对象。 需要注意的是, 内联函数提高代码性能的同时也会导致代码量的增加,所以应避免内联函数过大。3.应该在哪些地方使用 inline 标记?
(1) 如果您试图标记为内联函数的函数,并没有接收另一个函数作为参数,您将无法获得明显的性能提升,而且 IDE 甚至会建议您移除 inline 标记.
(2) 因为 inline 关键字可能会增加代码的生成量,所以一定要避免内联大型函数。举例来说,如果去查看 Kotlin 标准库中的内联函数,您会发现它们大部分都只有 1 - 3 行。
4.禁用内联(noinline)
如果您的函数有多个函数参数,但是您需要持有其中某个的引用时,您可以将对应的参数标记为 noinline。
internal object Utils {
@JvmStatic
inline fun calculate(a: Int, b: Int, title:()->Unit, cal: (Int, Int) -> Int): Int {
title()
return cal(a, b)
}
@JvmStatic
fun main(args: Array<String>) {
calculate(6, 3, { println("开始计算") },{ a, b -> a+b })
}
}
internal object Utils {
@JvmStatic
inline fun calculate(a: Int, b: Int,noinline title:()->Unit, cal: (Int, Int) -> Int): Int {
title()
return cal(a, b)
}
@JvmStatic
fun main(args: Array<String>) {
calculate(6, 3, { println("开始计算") },{ a, b -> a+b })
}
}
通过使用 noinline,编译器就只会为对应函数创建新的 Function 对象,其余的则依旧会被内联。
为了减少 lambda 表达式带来的额外内存分配,建议您使用 inline 关键字!只需注意,标记对象最好是接收一个 lambda 表达式作为参数的小型函数。如果您需要持有 (作为内联函数参数的) lambda 表达式的引用,或者想要将它作为参数传递给另一个函数,使用 noinline 关键字标记对应参数即可。
5.非局部返回
默认情况下,在高阶函数中,要显式的退出(返回)一个 Lambda 表达式,需要使用 return@标签的语法,不能使用裸return,但这样也不能使高阶函数和调用高阶函数的函数退出。
internal object Utils {
fun getResult(num:Int,sum:(Int)->Unit){
sum(num)
println("我在sum计算结束之后")
}
@JvmStatic
fun main(args: Array<String>) {
getResult(10){
it+10
println("$it+10=${it+10}")
return@getResult
}
println("我在调用getResult之后")
}
}
打印结果为:
但如果把 Lambda 表达式作为参数传递给一个内联函数,就可以在 Lambda 表达式中正常的使用return语句了,并且会使该内联函数和调用该内联函数的函数退出(返回),这种操作就是非局部返回。
internal object Utils {
@JvmStatic
fun main(args: Array<String>) {
getResult(10){
it+10
println("$it+10=${it+10}")
return
}
println("我在调用getResult之后")
}
inline fun getResult(num:Int,sum:(Int)->Unit){
sum(num)
println("我在sum计算结束之后")
}
}
打印结果为:
在使用了非局部返回后,Lambda 表达式中return的返回值受调用该内联函数的函数的返回值类型影响。
fun test(){
getResult(10){
it+10
println("$it+10=${it+10}")
return //此处的return 返回值类型需跟test函数的返回值类型一致
}
}
6.禁用非局部返回(crossinline)
内联函数可以使 Lambda表达式实现非局部返回,但是,如果一个内联函数的函数类型参数被crossinline修饰,则对应传入的 Lambda表达式将不能非局部返回了,只能局部返回了。
internal object Utils {
@JvmStatic
fun main(args: Array<String>){
getResult(10){
it+10
println("$it+10=${it+10}")
return@getResult
}
println("我在调用getResult之后")
}
inline fun getResult(num:Int, crossinline sum: (Int) -> Unit){
sum(num)
println("我在sum计算结束之后")
}
}
打印结果为:
7.具体化的类型参数(reified)
在java中如果是泛型,是不能直接使用泛型的类型的(可以通过反射做到),但是kotlin却是可以的。 在内联函数中支持具体化的参数类型,即用reified来修饰需要具体化的参数类型,这样我们用reified来修饰泛型的参数类型,以达到我们的目的。
inline fun <reified T : Activity> Activity.startActivity() {
startActivity(Intent(this, T::class.java))
}
startActivity<MainActivity>()
再比如需要创建一个Fragment的实例,并且要传递参数
之前你可能会这样写:
class MyFragment : Fragment() {
fun newInstance(param: Int): MyFragment {
val fragment = MyFragment()
val args = Bundle()
args.putInt("key", param)
fragment.arguments = args
return fragment
}
}
需要的Fragment都要写个这个,不够优雅而且太麻烦。 现在通过reified来优化:
internal object Utils {
inline fun <reified F : Fragment> Context.newFragment(key:String, value:String): F {
val bundle = Bundle()
bundle.putString(key, value)
return Fragment.instantiate(this, F::class.java.name, bundle) as F
}
}
调用:
newFragment<MyFragment>("name","lisa")