函数探究
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>
## 自动推断函数返回值
函数的返回值如果可以通过一行代码自动推断出来,就可以去掉`{}`,换上`=`
```kotlin
fun 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()
}
反编译
@NotNull
public 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());
}
@NotNull
public 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 + num2
println("外部调用了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就可以直接使用接受者方法
反编译
@NotNull
private static final Function2 block;
@NotNull
public 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调用