kotlin中有趣的用法,函数式编程

1.扩展函数

在 java中 api 定义许多好用的方法,但有些方法并不能解决我们的需求,经常看到项目中许多util类。
例如判断一个字符串是不是邮箱格式java 代码可能是这样的

public static boolean isEmail(String email) {
        return Pattern.compile("^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$")
                .matcher(email).matches();
    }
   ……
    public static void main(String[] args) {
        String str = "";
        StringUtil.isEmail(str);
    }

在 kotlin 中可以利用扩展函数,实现上述代码

//此方法不需要写在某个类中,任意一个.kt文件即可
//为某个类扩展,就在函数名前加上类名.最为前缀,方法体中可以使用 this 关键字
//获取到调用该方法的对象
fun String.isEmail(): Boolean =
    Pattern.compile("^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*\$")
        .matcher(this).matches()
fun main() {
    val str=""
    // 调用扩展函数,使用扩展类的对象就可以调用,看上去就像该类本身具有的成员方法
    println(str.isEmail())
}

kotlin 如何实现扩展函数的呢,通过产看 kotlin字节码转成 java 代码你就明白了
下面是转换之后的代码

  public static final boolean isEmail(@NotNull String $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "receiver$0");
      return Pattern.compile("^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$").matcher((CharSequence)$receiver).matches();
   }

发现代码和我们以前的写的 java 代码差不多,只不过 kotlin 的语法可以实现扩展函数的样子
kotlin api提供许多有用的扩展函数,如一些类型转换的扩展函数,toInt()、toIntOrNull()、toFloat()、toLong、toXXX()……
例源码中的 toInt()

public actual inline fun String.toInt(): Int = java.lang.Integer.parseInt(this)

2.kotlin 中的高阶函数——函数可以当做参数或返回值

kotlin 是函数式编程,kotlin 中函数也是一种类型。例如

	//block1 无参数无返回值函数类型
    val block1: () -> Unit
    // block2 无参数返回值为 String 的函数类型
    val block2: () -> String
    // block2 有一个 Int 类型的参数无返回值的函数类型
    val block3: (Int) -> Unit
    // block4 有2个参数返回值为 String 的函数类型
    val block4: (Int, String) -> String

函数类型对应在kotlin.jvm.functions 中都有定义

package kotlin.jvm.functions

/** A function that takes 0 arguments. */
public interface Function0<out R> : Function<R> {
    /** Invokes the function. */
    public operator fun invoke(): R
}
/** A function that takes 1 argument. */
public interface Function1<in P1, out R> : Function<R> {
    /** Invokes the function with the specified argument. */
    public operator fun invoke(p1: P1): R
}
/** 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
}

这个文件中定义了从 Function0 到 Function22 接口,Function后面的数字代表可以有几个参数,
Function0<out R>代表无参数返回值为指定泛型 R,Function1<in P1, out R>代表1个参数返回值为指定泛型 R, 同理Function22就是有22个参数返回值为指定泛型 R。

//根据上述对应定义关系,block1、block2、block3、block4 就可以写成下面的形式
	//block1 无参数无返回值函数类型
    val block1: Function0<Unit>
    // block2 无参数返回值为 String 的函数类型
    val block2: Function0<Unit>
    // block2 有一个 Int 类型的参数无返回值的函数类型
    val block3: Function1<Int,Unit>
    // block4 有2个参数返回值为 String 的函数类型
    val block4: Function2<Int,String,String>

在 kotlin.jvm.functions 中定义到 Function22 ,那么函数类型最多只能定义有22个参数的函数类型?(22 个参数还够你用?)
在 kotlin1.3之前定义超过22个参数的函数类型确实不行,会报错,在 kotlin1.3版本加入一个 FunctionN 可以对应参数大于22的函数类型。kotlin.jvm.functions 中定义的都是一些 Function 的接口,具体实现有兴趣的可以去研究一下。
在 kotlin 中函数既然是一种类型,那么定义函数的参数或返回值时,也可以把函数类型作为参数或者返回值
如下面函数作为参数

fun show(block: () -> Unit) {
    println("start")
    //调用传入的函数,
    //从Function0定义中有一个operator 修饰的方法表示操作符重载的方法,
    // block()完整写法 block.invoke()
    block()
    println("end")
}
fun main() {
//调用 show 方法传如一个函数
    show {
        println("函数类型参数")
    }
}

输出结果

start
函数参数
end

为什么show后面跟上一个{}可以就可以调用 show 方法?

先说说函数类型如何实例化吧
如定义val block:(Int) ->String 实例方式如下
通过lambda 表达式的方式实现

val block:(Int) ->String={params:Int-> "函数类型实例化"}

通过匿名函数实现

 val block: (Int) -> String = fun(params: Int): String { return "函数类型实例化" }

说完函数类型实例化再看 show 方法,show 方法需要的参数是 () -> Unit 那么我们需要一个这钟类型的实例

  //使用lambda 表达式创建函数类型实例
    val block = { print("函数类型参数") }
    //调用函数
    show(block)
    //在 kotlin中最后一个参数如果是 lambda 那么把 lambda 表达式写到()外面
    show() { print("函数类型参数") }
    //如果lambda前面括号没有任何参数可以省去不写,最终写法就是
    show { print("函数类型参数") }

利用上述特性我们可以写出许多有意思的代码,例如 kotlin 可以使用很简洁的方式开启一个线程,代码如下

//在{}写以前 run 方法里面写的逻辑即可,再也不用担心线程忘记start()了
 thread {
        println(Thread.currentThread().name)
    }

查看该方法源码就会知道就利用函数类型作为参数很简单就实现了上面简洁的写法,附上thread 方法源码

public fun thread(
    start: Boolean = true,
    isDaemon: Boolean = false,
    contextClassLoader: ClassLoader? = null,
    name: String? = null,
    priority: Int = -1,
    block: () -> Unit
): Thread {
    val thread = object : Thread() {
        public override fun run() {
            block()
        }
    }
    if (isDaemon)
        thread.isDaemon = true
    if (priority > 0)
        thread.priority = priority
    if (name != null)
        thread.name = name
    if (contextClassLoader != null)
        thread.contextClassLoader = contextClassLoader
    if (start)
        thread.start()
    return thread
}

3.闭包

闭包概念比较抽象,先看一段代码

/**
 * @return ()->Int 返回函数类型
 */
fun test(): () -> Int {
    var count = 1
    return { count++ }//使用lambda表达式创建函数实例返回
}
fun main() {
    //调用test方法返回一个()->Int函数类型实现
    val show = test()
    //调用返回是函数,每次调用返回值不同
    println(show())//1
    println(show())//2
    println(show())//3
}

输出结果

1
2
3

一般方法执行完就会出栈,方法中局部变量也会随之释放。但上面代码看上去却不是那样的,很奇怪。test() 执行完后 再调用show()方法是count这个局部变量应该是找不到才对,但运行的结果却是每次调用结果都加一了。

下面将kotlin编译成java应该就可以解释着这个问题了

 @NotNull
   public static final Function0 test() {
      final IntRef count = new IntRef();
      count.element = 1;
      return (Function0)(new Function0() {
         // $FF: synthetic method
         // $FF: bridge method
         public Object invoke() {
            return this.invoke();
         }

         public final int invoke() {
            IntRef var10000 = count;
            int var1;
            var10000.element = (var1 = var10000.element) + 1;
            return var1;
         }
      });
   }

在kotlin中test()中定义的count变量在java中变成了对象引用。对象随之方法的调用而创建,但是有引用指向对象,方法执行完,对象是不会被释放的,所以每次调用改变的是的该对象的属性值。

如果不转换为java代码可以用闭包的概念理解这段代码。count和函数{count++}在test()方法中创建,count是一个自由变量(非局部变量称之为自由变量,count不会随着show方法的调用而消失。自由变量要比局部变量生命周期长)。函数{count++}引用自由变量count。在维基百科闭包是这个样定义的是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

按照维基百科的第一种说法{count++}就是闭包,它是应用了自由变量的函数。如何按第二种说法{count++}和它执行的引用环境整体是一个闭包。

参考链接
kotlin 函数
维基百科 闭包

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章