美文网首页Kotlin精选
揭露Kotlin 高阶函数的真面目

揭露Kotlin 高阶函数的真面目

作者: 想去山上定居 | 来源:发表于2017-06-02 18:43 被阅读0次

    高阶函数:可以接受函数作为参数或/并返回一个函数的函数

    看一断诡异的代
    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>,这个是一个定义操作符的关键字,这里只讲解定义函数操作符的方式:

    1. 使用operator 修饰函数
    2. 函数名必须为invoke
    3. 函数必须为成员函数,就是类或者接口的函数

    总结

    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)
    }
    

    解析

    1. funObject 对象为(Int, String) -> Int函数类型
    2. { a, b -> "$b$a".toInt() } lambda 表达式,关于lambda 表达式的讲解已经超出本文范围
    3. 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个

    总结

    1. Kotlin 给出23 个固定函数类型接口
    2. 在Kotlin 中可以使用(P0,P1,...)->R 方式声明一个对象为固定函数类型接口,只能是Kotlin 给出的23 个
    3. 可以使用lambda 表达式现实这个固定的23 个接口,其他接口需要使用object:Interface{...} 方式现实
    4. 定制了函数操作符的类或者接口的对象的类型为函数类型
    5. 在Kotlin中,函数类型对象可以直接当成函数使用,例如:funObject()

    最后给出一个编程成功,运行失败的例子:形参个数超过22 个,那么Kotlin 编译器是可以编译通过的,之后运行当然找不到对象的固定函数类型接口,最后运行错误

    image.png

    java.lang.NoClassDefFoundError: kotlin/Function84,看来我不小心写了84 个参数了。

    高阶函数的真面目

    所以函数作为参数、返回一个函数,其实就是一个固定接口的对象。这就是高阶函数的真面目。

    相关文章

      网友评论

        本文标题:揭露Kotlin 高阶函数的真面目

        本文链接:https://www.haomeiwen.com/subject/yubufxtx.html