函数探究
kotlin中的函数不同于Java中的方法,函数在kotlin中是一级公民,也就是说函数可以作为引用,出现在变量、参数、返回值等其它类能出现的位置,并且使用函数的引用可以直接调用函数。
分析函数的实质
在数学中,函数就是一个映射,从定义域到值域的一一对应关系。
如果把函数当做引用,那么重要的内容是什么呢?
- 定义域:即为方法的入参类型
- 值域:即为方法的返回值类型
- 函数的具体逻辑:这个对应的不是引用,而是引用的实体,所以在函数引用中,具体逻辑是不需要在引用中实现的
函数应用
作用范围
函数的作用域与其它引用无异,范围有以下等
- 变量
- 参数
- 返回值 ```kotlin // 函数作为顶部变量 var function1: ((i: Int) -> String)? = null
// 函数作为参数 var function2: (((Int) -> String) -> Unit)? = null
// 函数作为返回值 var function3: (() -> (Int) -> String)? = null
**tips:**函数作为引用的时候,参数的类型必须加上,而参数的名称可以省略<br />(这很符合数学函数的特性,定义域 x∈(1,2)或者 a∈(1,2),都不影响函数的表达)<a name="oMRP5"></a>## 自动推断函数返回值函数的返回值如果可以通过一行代码自动推断出来,就可以去掉`{}`,换上`=````kotlinfun add(i: Int, j: Int) = i + j
有时候复杂的情形不容易一眼看出,但是只要找到 fun 或者 ->就可以知道这是一个函数
inline fun <reified T : ViewBinding> inflateBinding(layoutInflater: LayoutInflater) =T::class.java.getMethod("inflate", LayoutInflater::class.java).invoke(null, layoutInflater) as T
函数赋值
函数当做引用,当然需要赋值的时刻,就相当于类引用被赋值,才能真正执行
// 接收一个函数作为参数fun addFunction(num1: Int, num2: Int, block: (Int, Int) -> Int) {val num3 = block(num1, num2)println("我是调用了传入函数 $block 计算了2个值 : $num3")}// 测试函数的完整调用fun invokeAddFunction() {// 1.完整调用addFunction(1, 2, fun(i: Int, j: Int): Int {return i + j})// 2.完整调用+省略参数类型addFunction(1, 2, fun(i, j): Int {return i * j})// 3.完整调用+省略参数类型+自动推断返回值addFunction(1, 2, fun(i, j) = i + j)// 4.lambda表达式调用addFunction(1, 2, { i: Int, j: Int ->i * j})// 5.lambda表达式调用+省略参数类型addFunction(1, 2, { i, j ->i + j})// 6.lambda表达式调用+省略参数类型+最后一个参数为lambda可以写在()外部addFunction(1, 2) { i, j ->i * j}// 7.使用方法引用作为函数addFunction(1, 2, ::add)}
总结:
- 使用
fun完整模式,但是写起来代码多,很少使用 - 使用
lambda表达式,省略参数类型,最后一行作为返回值,参数最后一个为lambda的时候可以写在()外面 - 使用
::,这是方法的是引用,Java中也有,只要方法接收的参数类型、个数、返回值与函数的引用相同,即可以直接传入方法引用
常用函数
内联函数
内联函数是kotlin中一种特殊函数,与C的内联特类似,都是把代码拷贝到使用的位置
优点:
- 减少内存的开销
- 可以使用refine等真实泛型
缺点:
- 增加代码量,所以内联函数不宜内部代码过多
内联函数和普通函数的区别
// 内联函数inline fun attack(i: Int, block: (Int) -> Int): String {return block(i).toString()}// 普通函数fun attack2(i: Int, block: (Int) -> Int): String {return block(i).toString()}
反编译
@NotNullpublic static final String attack(int i, @NotNull Function1 block) {int $i$f$attack = 0;Intrinsics.checkNotNullParameter(block, "block");return String.valueOf(((Number)block.invoke(i)).intValue());}@NotNullpublic static final String attack2(int i, @NotNull Function1 block) {Intrinsics.checkNotNullParameter(block, "block");return String.valueOf(((Number)block.invoke(i)).intValue());}
结论:
- 内联函数与普通函数没有任何区别
Kotlin调用内联函数
fun main() {// kotlin调用内联函数、普通函数的区别attack(1) { i -> i + 1 }println("--------------------------")attack2(1) { i -> i + 1 }}
反编译
public static final void main() {int i$iv = 1; // 内联函数直接代码拷贝int $i$f$attack = false;int var3 = false;String.valueOf(i$iv + 1);String var4 = "--------------------------";$i$f$attack = false;System.out.println(var4);attack2(1, (Function1)null.INSTANCE); // 普通函数传Function1}
结论:
- 内联函数直接拷贝代码到了调用处,没有使用接口
- 普通函数使用了自动生成的接口Function1
Java调用内联函数
public static void main(String[] args) {// 没有任何区别,都是传入Function1回调,所以java不存在内联特性KtInlineKt.attack(1, i -> i + 1);KtInlineKt.attack2(1, i -> i + 1);}
结论:
- Java不存在内联特性
扩展函数
Kotlin中最常用的函数特性就是扩展函数,可以对已知类无侵入式的扩展,并且能直接调用,猜测扩展函数是通过装饰模式(代理模式)实现的
探究扩展函数的实质
// 一个简单Kotlin类,等待被扩展class Source {private val tag = "普通Kotlin类Source"fun plus(num1: Int, num2: Int): Int {println("$tag ---调用了plus()")return num1+num2}}
// 扩展函数fun Source.plusPlus(num1: Int, num2: Int) {// 这里取到的this,是Source对象val plus = this.plus(num1, num2)val outPlus = plus + num2println("外部调用了plusPlus,多加了1次num2,结果为:$outPlus")}
反编译
public final class SourceExtendKt {public static final void plusPlus(@NotNull Source $this$plusPlus, int num1, int num2) {Intrinsics.checkNotNullParameter($this$plusPlus, "$this$plusPlus");int plus = $this$plusPlus.plus(num1, num2);int outPlus = plus + num2;String var5 = "外部调用了plusPlus,多加了1次num2,结果为:" + outPlus;boolean var6 = false;System.out.println(var5);}}
结论:
- 扩展函数并不改变原类
- 扩展函数也不是装饰模式,而是根据类名生成了一个新类
- 接收了一个原类的对象为参数,同时接收扩展函数自己定义的参数
使用扩展函数
Kotlin使用
fun main() {val source = Source()source.plusPlus(1,2)}
非常简单,就像调用原来类上的函数一样,反编译看看
public final class KtInvokeExtendKt {public static final void main() {Source source = new Source();SourceExtendKt.plusPlus(source, 1, 2);}}
结论:
- kotlin调用扩展很方便,在被扩展的原类上调用即可
- 实质上,并不是修改了原类,而是通过扩展方法,接收原类对象来调用
Java使用
public static void main(String[] args) {// source对象没有plusPlus方法,所以kotlin函数并没有真正修改Source类Source source = new Source();source.plus(1, 2);// 扩展函数是 SourceExtendKt,没有plus方法,并且需要传入Source对象SourceExtendKt.plusPlus(source, 1, 2);}
结论:
- Java使用扩展函数就像反编译kotlin使用代码一样
A.(B) -> C 类型函数
函数类型可以有一个额外的接收者类型,它在表示法中的点之前指定: 类型 A.(B) -> C 表示可以在 A 的接收者对象上以一个 B 类型参数来调用并返回一个 C 类型值的函数
在这样的函数字面值内部,传给调用的接收者对象成为隐式的_this_,以便访问接收者对象的成员而无需任何额外的限定符,亦可使用 this表达式 访问接收者对象。
这种行为与扩展函数类似,扩展函数也允许在函数体内部访问接收者对象的成员。
class Amber {fun attack(i: Int) {println("Amber 攻击了${i}次")}}
// A.(B) -> C 函数val block: Amber.(String) -> Amber = { str ->// 内部空间在Amber对象内,可以直接调用Amber的方法attack(str.toInt())this}
A.(B) -> C 函数的好处在于函数内部直接就是接受者对象的范围,用this就可以直接使用接受者方法
反编译
@NotNullprivate static final Function2 block;@NotNullpublic static final Function2 getBlock() {return block;}/** A function that takes 2 arguments. */public interface Function2<in P1, in P2, out R> : Function<R> {/** Invokes the function with the specified arguments. */public operator fun invoke(p1: P1, p2: P2): R}
结论:
- 额外接受者类型函数,还是把接收类型的对象+参数生成了Function接口
- 那么Kotlin使用就有2种方式
- Java调用只有1种方式
验证上面的结论
fun main() {// 使用方法1,函数引用block作为调用者block(Amber(), "12")// 使用方法2,接收函数的对象作为调用者,block作为方法Amber().block("13")}
反编译
public static final void main() {block.invoke(new Amber(), "12");block.invoke(new Amber(), "13");}
结论:
- Kotlin调用方法确实有2种
- 通过接收类对象调用,是kotlin的语法而已,实质还是函数调用
相关内容
常用的kotlin扩展函run(),apply()等就是通过此类型函数实现内部范围this调用
