阅读 722

Kotlin进阶知识(六)——声明高阶函数

一、函数类型

Kotlin的显式类型声明是:

val sum = { x: Int, y: Int -> x + y }
val action = { println(42) }

// 有两个Int型参数和Int型返回值的函数
val sum: (Int, Int) -> { x, y -> x + y }
// 没有参数和返回值的函数
val action(): -> Unit = { println(42) }
复制代码

声明函数类型,需要将函数参数类型放在括号中,紧接着是一个箭头和函数的返回类型,如下:

(Int, String)    ->       Unit
|- 参数类型 -|     |- 返回类型 -|
复制代码

Unit类型用于表示函数不返回任何有用的值。在声明一个普通的函数时,Unit类型的返回值是可以省略的,但是一个函数类型声明总是需要一个**显式的返回类型**,所以在这种场景下**Unit不能省略**的。

函数类型的返回值是可空类型 & 函数本身可空

  • 函数类型的返回值是可空类型
var canReturnNull: (Int?, Int?) -> Int? = { x, y ->
    y?.let {
        x?.plus(it)
    }
}

//测试
println(canReturnNull(1, 2))
println(canReturnNull(null, 2))

//输出结果
3
null
复制代码
  • 函数本身可空
var funOrNull: ((Int, Int) -> Int) ? = null

//测试
println(funOrNull?.let { it(1, 2) })

//输出结果
null
复制代码

**注意:**如果省略了括号,声明的将会是一个返回值可空的函数类型,而不是一个可空的函数类型的变量.

注意该函数本身可空时调用需要使用**类型**

二、调用作为参数的函数

// 定义一个函数类型的参数:operation
fun twoAndThree(operation: (Int, Int) -> Int) {
    // 调用函数类型的参数
    val result = operation(2, 3)
    println("The result is $result")
}

fun twoAndThreeTest() {
    twoAndThree{ a, b -> a + b }
    twoAndThree{ a, b -> a * b }
}

// 输出结果
The result is 5
The result is 6
复制代码

调用作为参数的函数和调用普通函数的语法是一样的:把括号放在函数名后,并把参数放在括号内

函数声明的示意图1: 图1:filter函数的声明,以一个判断式作为参数

filter函数以一个判断式作为参数。判断式的类型是一个函数,以字符作为参数返回boolean类型的值。如果要让传递给判断式的字符出现在最终返回的字符串中,判断式需要返回true,反之返回false。

fun String.filter(predicate: (Char) -> Boolean): String {
    val result = StringBuilder()
    for(index in 0 until length) {
        val element = get(index)
        // 调用作为参数传递给“predicate”的函数
        if(predicate(element)) result.append(element)
    }
    return result.toString()
}

fun stringFilterTest() {
    // 传递一个lambda作为“predicate”参数
    println("abcABC123".filter {
        it in 'a' .. 'z'
    })
}

// 输出结果
abc
复制代码

三、在Java中使用函数类

函数类型被声明为普通的接口:一个函数类型的变量时FunctionN接口的一个实现。Kotlin标准库定义了一系列的接口,这些接口对应于不同参数树立的函数:Function0(没有参数的函数)、Function1<P1, R>(一个参数的函数),等等。每个接口定义了一个invoke方法,调用这个方法就会执行函数。

一个函数类型的变量就是实现了对应的**FunctionN接口的实现类的实例**,实现类的**invoke**方法包含了lambda函数体。

/* kotlin 声明 */
fun processTheAnswer(f: (Int) -> Int) {
    println(f(42))
}

/* Java */
// 调用processTheAnswer方法
// 在Java代码中使用函数类型(Java8以前)
    public static void processTheAnswerTest() {
        processTheAnswer(
                new Function1<Integer, Integer>() {
                    @Override
                    public Integer invoke(Integer number) {
                        System.out.println(number);
                        return number + 1;
                    }
                }
        );
    }

// Java 8
processTheAnswer(number -> number + 1)
复制代码

在Java中可以很容易地使用Kotlin标准库中以lambda作为参数的扩展函数。

但是要注意:必须要显式地传递一个接受者对象作为第一个参数:

    public void printString() {
        List<String> strings = new ArrayList<>();
        strings.add("42");
        // 可以在Java代码中使用Kotlin标准库中的函数
        CollectionsKt.forEach(strings, s -> {
            System.out.println(s);
            // 必须要显式地返回一个Unit类型的值
            return Unit.INSTANCE;
        });
    }
复制代码

在**Java中,函数或者lambda可以返回Unit。但因为在Kotlin中Unit类型是有一个值的,所以需要显式地返回**它。

注意:一个返回voidlambda不能作为返回Unit的函数类型的实参! 就像之前的例子中的(String) -> Unit

四、返回函数的函数

enum class Delivery { STANDARD, EXPEDITED }

class Order(val itemCount: Int)

fun getShippingCostCalculator(
    // 声明一个返回函数的函数
    delivery: Delivery): (Order) -> Double {
    if(delivery == Delivery.EXPEDITED) {
        // 返回lambda
        return { order -> 6 + 2.1 * order.itemCount }
    }

    // 返回lambda
    return { order -> 1.2 * order.itemCount }
}

fun getShippingCostCalculatorTest() {
    // 将返回的函数保存在变量中
    val calculator =
        getShippingCostCalculator(Delivery.EXPEDITED)

    // 调用返回的函数
    println("Shipping costs ${calculator(Order(3))}")
}

//  输出结果
Shipping costs 12.3
复制代码

声明一个返回另一个函数的函数,需要指定一个函数类型作为返回类型。

在上述代码中,getShippingCostCalculator返回了一个函数,这个函数以Order作为参数并返回一个Double类型的值。

返回一个函数,需要写一个return表达式,跟上一个lambda一个成员引用,或者其他的函数类型的表达式,比如一个(函数类型的)局部变量。