高阶函数:可以接受函数作为参数或/并返回一个函数的函数
看一断诡异的代
class Age {
operator fun invoke(offset:Int): Int {
return 9 + offset
}
}
fun main(args: Array<String>) {
var age = Age() //实例化一个对象
var value = age(2) //把对象当初函数直接用了,好奇怪哦
println(value)
}
上面代码之后把一个对象当成函数运行了,细心的同学应该看到了Age 类唯一的方法前面多了一个关键字 <code>operator</code>,这个是一个定义操作符的关键字,这里只讲解定义函数操作符的方式:
- 使用operator 修饰函数
- 函数名必须为invoke
- 函数必须为成员函数,就是类或者接口的函数
总结
1. () 是函数操作符
2. 如果一个类或者接口定制了函数操作符,那么该类或者接口的对象能直接使用函数操作符调用定制的函数
函数类型(fun类型)
如果一个类或者接口定制了函数操作符,那么该类或者接口的对象的类型称为函数类型,简称 fun类型,该对象成为 函数类型对象,简称 fun对象
class FunClass {
//定制了函数操作符
operator fun invoke(): Int {
return 9
}
}
interface FunInterface {
//定制了函数操作符
operator fun invoke(): Int
}
fun main(args: Array<String>) {
var funObject1: FunClass //该对象为函数对象
var funObject2: FunInterface //该对象为函数对象
}
上面代码中funObject1,funObject2 的类型为fun, 9 的类型为Int,9L 类型为Long,funObject1 类型为 fun
总结
函数对象能当成函数直接调用
//例如上面的对象,可以直接当初函数使用 funObject1() funObject2()
Kotlin 的语法糖
-- 吃起来挺甜的,但使用的材料能和地沟油拼
Java 和Kotlin 在本质上是没有区别的,编译后的代码都是在JVM 上运行,Kotlin 还是和Java 100% 兼容,那么能得出一个结论就是,Kotlin 编译好的代码能反编译成Java 代码。激动了,必须尝试了。
class FunClass {
operator fun invoke(): Int {
return 9
}
}
fun main(args: Array<String>) {
var funObject = FunClass()
var value = funObject()
println(value)
}
|反|
|编|
|译|
\_./
public final class A18Kt {
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
FunClass funObject = new FunClass();
int value = funObject.invoke(); //我就知道,不就改为直接调用方法嘛
System.out.println(value);
}
}
public final class FunClass {
public final int invoke() {
return 9;
}
}
反编译后可以看到,<code>funObject()</code> 转变为<code>funObject.invoke()</code>,其他的转换就不一一解释了,比较简单,认真看看基本能明白。
语法糖解析:
在编译Kotlin 代码编译之前有一个预编译(语法糖基本是这个阶段起作用),只要在预编译阶段把<code>funObject()</code> 改变为<code>funObject.invoke()</code> 就可以了,之后的编译就不成问题了。这个就是语法糖,是不是很甜?记住一点:语法糖 == 修改代码
FunClass 类的作用就是invoke 的函数体,直接定义成interface,哪里需要哪里实现
interface FunInterface {
operator fun invoke(): Int
}
fun main(args: Array<String>) {
var funObject = object:FunInterface{
override fun invoke(): Int {
return 9
}
}
var value = funObject()
println(value)
var funObject2 = object : FunInterface {
override fun invoke(): Int {
return 8
}
}
var value2 = funObject2()
println(value2)
}
|反|
|编|
|译|
\_./
public interface FunInterface {
int invoke();
}
public final class A16Kt {
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
<undefinedtype> funObject = new FunInterface() {
public int invoke() {
return 9;
}
};
int value = funObject.invoke();
System.out.println(value);
<undefinedtype> funObject2 = new FunInterface() {
public int invoke() {
return 8;
}
};
int value2 = funObject2.invoke();
System.out.println(value2);
}
}
Kotlin 匿名类实现接口的方式为object:InterfaceName{...} 更多object 的使用已经超出了本文的范围,下次再讲解
反编译后的代码请自行对比,你会发现还是代码的转变。
进一步接近函数类型
如果我预先定义好一些接口,包含常用的函数,那么以后哪里都用这些接口就好了,例如
interface FunInterface0 {
operator fun invoke()
}
interface FunInterface1<R> {
operator fun invoke():R
}
interface FunInterface2<P, R> {
operator fun invoke(p: P): R
}
interface FunInterface3<P0, P1, R> {
operator fun invoke(p0: P0, p1: P1): R
}
//使用方式改为
fun main(args: Array<String>) {
var funObject = object : FunInterface1<Int> {
override fun invoke(): Int {
return 9
}
}
var value = funObject()
println(value)
var funObject2 = object : FunInterface2<Int,Int> {
override fun invoke(p:Int): Int {
return 8 + p
}
}
var value2 = funObject2(3)
println(value2)
}
可以看到函数类型还有区别的,由函数形参个数、形参类型和返回值类型决定,三个因素已经决定了函数类型,那么就可以定制简单的写法,Kotlin 中使用()和->来定制,例子如下
()->Int //无参返回Int类型的函数类型
(Int)->Int //1个Int类型形参,返回值为Int 的函数类型
(Int,String)->Unit //2个形参,类型为Int和String,没有返回值
//... 其他的也是类型的
对象声明使用定制的写法,接口的实现使用lambda 表达式简写,那么写法为:
fun main(args: Array<String>) {
var funObject: (Int, String) -> Int = { a, b ->
println(b)
a
}
var value = funObject(4, "99")
println(value)
}
解析
- funObject 对象为(Int, String) -> Int函数类型
- { a, b -> "$b$a".toInt() } lambda 表达式,关于lambda 表达式的讲解已经超出本文范围
- funObject(4, "99") 函数类型可以直接当成函数使用
|反|
|编|
|译|
\_./
public final class A15Kt {
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
Function2 funObject = (Function2)null.INSTANCE;
int value = ((Number)funObject.invoke(Integer.valueOf(4), "99")).intValue();
System.out.println(value);
}
}
//有点不对哦,Function2 的实现无法反编译出来,下面给一个可行的实现
public class A17 {
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
Function2 funObject = (Function2) new Function2<Integer, String, Integer>() {
@Override
public Integer invoke(Integer a, String b) {
System.out.println(b);
return a;
}
};
int value = ((Number)funObject.invoke(Integer.valueOf(4), "99")).intValue();
System.out.println(value);
}
}
真相已经出来了,多了一个Function2,查看Function2 的源代码你会惊人地发现下面接口
image.png还要多说什么吗?Kotlin 给出固定的23 个函数类型接口,最多形参个数为22个
总结
- Kotlin 给出23 个固定函数类型接口
- 在Kotlin 中可以使用(P0,P1,...)->R 方式声明一个对象为固定函数类型接口,只能是Kotlin 给出的23 个
- 可以使用lambda 表达式现实这个固定的23 个接口,其他接口需要使用object:Interface{...} 方式现实
- 定制了函数操作符的类或者接口的对象的类型为函数类型
- 在Kotlin中,函数类型对象可以直接当成函数使用,例如:funObject()
最后给出一个编程成功,运行失败的例子:形参个数超过22 个,那么Kotlin 编译器是可以编译通过的,之后运行当然找不到对象的固定函数类型接口,最后运行错误
image.pngjava.lang.NoClassDefFoundError: kotlin/Function84,看来我不小心写了84 个参数了。
高阶函数的真面目
所以函数作为参数、返回一个函数,其实就是一个固定接口的对象。这就是高阶函数的真面目。
网友评论