Kotlin基础知识五: lambda表达式

lambda expression的语法

语法: {Parameters ->Body}
举例:

val sum = { x: Int, y: Int -> x + y } //store a lambda expression in a variable.
println(sum(2, 5)) //treat this variable like a normal function.

与局部变量一样,lambda表达式中的参数可以自动推断出来。
如果一个lambda expression是一个函数的最后一个参数,那么可以将该lambda expression移到函数的小括号外面;

val people = listOf(Person("Alice", 29), Person("Bob", 31))
val names = people.joinToString(separator = " ",
        transform = { p: Person -> p.name })

可以简化为:

people.joinToString(" ") { p: Person -> p.name }

如果一个lambda expression是一个函数的唯一参数,那么可以进一步的省去函数的小括号。

people.maxBy { p: Person -> p.age } //显式指定参数类型
people.maxBy { p -> p.age } //参数类型自动推断而出

也可以使用默认的参数名字it:

people.maxBy { it.age }

如果把lambda表达式赋值给一个变量,则没有推断可以参数类型的上下文,所以需要显式指定参数类型:

val getAge = { p: Person -> p.age }
people.maxBy(getAge)

lambda表达式并不仅限于一个表达式或一条语句,此时最后一个expression的值为lamada的结果。

fun main() {

    val sum = { x: Int, y: Int ->
        println("Computing the sum of $x and $y...")
        x + y
        var a = 10
    }

    println(sum(10, 20)) //kotlin.Unit
}

lamda优于匿名内部类

Don’t use anonymous classes for function objects unless you have to create instances of types that aren’t functional interfaces.

lambda表达式的局限性

  1. Lambda仅限于函数式接口。 如果要创建一个abstract class的实例,则只能使用anonymous class;
  2. 类似的,如果要创建具有多个抽象方法的接口的实例,也只能使用anonymous class;
  3. 不同于匿名类,lambda中this是“调用lambda表达式的对象”,而匿名内部类中的this表示的是“匿名内部类对象本身”。
    举例:
package com.sample.java;

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class JavaSample {

    private static Runnable lambda1 = () -> {
        System.out.println("lambda1");
        //System.out.println("lambda1 this: " + this.getClass()); //'this' cannot be referenced from a static context.
    };

    private static Runnable runnable = new Runnable() {
        @Override
        public void run() {
            //runnable this: com.sample.java.JavaSample$1
            System.out.println("runnable this: " + this.getClass());
        }
    };

    public static void main(String[] args) {
        new Thread(lambda1).start();
        new Thread(runnable).start();
        new InnerClass().testLambda();
    }

    private static class InnerClass {
        private Comparator<String> lambda2 = (String s1, String s2) -> {
            System.out.println("lambda2 this: " + this.getClass()); //com.sample.java.JavaSample$InnerClass
            return Integer.compare(s1.length(), s2.length());
        };

        private void testLambda() {
            List<String> list = Arrays.asList("hello", "world");
            Collections.sort(list, lambda2);
        }
    }
}

在作用域内访问变量

Lameda表达式访问外部变量时,不要求是final类型,这意味着可以在lambda表达式中修改外部变量,我们称之为被lambda表达式captured。举例:

package com.example.kotlin.ch05

fun printlnProblemCounts(response: Collection<String>) {
    var clientErrors = 0
    var serverErrors = 0

    response.forEach {
        if (it.startsWith('4')) {
            clientErrors++
        } else if (it.startsWith('5')) {
            serverErrors++
        }
    }

    println("$clientErrors client errors, $serverErrors server errors")
}

fun main() {
    val responses = listOf(
        "200 OK", "418 I'm a teapot",
        "500 Internal Server Error"
    )

    printlnProblemCounts(responses)
}

注意:默认情况下,如果一个local variable被lambda表达式captured的话,使用该variable的代码可以先暂存起来、稍后再执行。
当捕获一个final variable时,它的值与lambda的代码保存在一起;
当捕获一个non-final variable时,它的值被封装在一个特殊的wrapper当中,这个wrapper的引用与lambda的代码保存在一起。

Member references

method reference type.png

Member references即对function的引用,使用::操作符可以把function转化成value:

val getAge = Person::age
people.maxBy(Person::age)

对顶层函数的引用:

fun salute() = println("Salute!")
run(::salute)

Constructor reference.

使用constructor reference可以将创建一个实例的动作保存在value中。

data class Person(val name: String, val age: Int)
//The action of creating an instance of “Person” is saved as a value.
val createPerson = ::Person
val p = createPerson("Alice", 29)
println(p) //Person(name=Alice, age=29)

引用extension functions也是类似:

fun Person.isAdult() = age >= 21
val predicate = Person::isAdult

函数式接口(functional interfaces)

仅有一个抽象方法的接口称之为函数式接口(functional interfaces),又称为SAM(single abstract method)接口。比如Java中Runnable和Callable这种。这种接口的实例称之为函数式对象(function objects)。
任何期待functional interface类型参数的地方,都可以给它传递一个lambda表达式。
举例:

void postponeComputation(int delay, Runnable computation);

使用kotlin调用时:

postponeComputation(1000) { println(42) }

编译器会自动将lambda转化为"实现了Runnable接口的匿名类的实例"。

postponeComputation(1000, object : Runnable {
    override fun run() {
        println(42)
    }
})

Collections中的函数式APIs

  1. filter
    filter会从collections中移除不想要的元素,但是并不改变元素本身。


    filter.png
val list = listOf(1, 2, 3, 4, 5)
val evenList = list.filter { it % 2 == 0 }
println(evenList) //[2, 4]
  1. map
    map函数应用指定的function到collections中的每一个元素,并收集结果到一个新的collection中。


    map.png
val list = listOf(1, 2, 3, 4, 5)
val mapList = list.map { it * it }
println(mapList) //[1, 4, 9, 16, 25]

如果只想取出Person列表中Person的名字,那么可以这样写:

val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.map { it.name })

使用member references的形式:

people.map(Person::name)

也可以使用调用链,比如打印年龄大于30岁的人的名字:

people.filter { it.age > 30 }.map(Person::name)

找到年龄最大的那个人:

val maxAge = people.maxBy { it.age ?: 0 }?.age"
println(people.filter { it.age == maxAge })

"all", "any", "count"和"find":在collection上应用一个predicate

"group by" : 将一个list转化为一组maps

val people = listOf(
        Person("Alice", 31),
        Person("Bob", 29), Person("Carol", 31)
    )

println(people.groupBy { it.age })

output:
{31=[Person(name=Alice, age=31), Person(name=Carol, age=31)], 29=[Person(name=Bob, age=29)]}
如图:


Figure 5.5 The result of applying the groupBy function

返回类型是:Map<Int, List<Person>>

看另外一个例子,通过String的首字符进行分组:

val list = listOf("a", "ab", "b")
println(list.groupBy(String::first))

输出:
{a=[a, ab], b=[b]}

flatMap和flatten:处理嵌套的Collections中的元素

flatMap主要做了两件事情:

  1. 将Collection中的每一个元素应用function argument指定的函数,转换成一个list,这一步称之为map;
  2. 将这些lists压缩成一个list,这一步称之为flatten。

举例:

class Book(val title: String, val authors: List<String>)

fun main() {
    val strings = listOf("abc", "def")
    println(strings.flatMap { it.toList() }) //[a, b, c, d, e, f]

    val books = listOf(
        Book("Thursday Next", listOf("Jasper Fforde")),
        Book("Mort", listOf("Terry Pratchett")),
        Book("Good Omens", listOf("Terry Pratchett", "Neil Gaiman"))
    )

    println(books.flatMap { it.authors }) //[Jasper Fforde, Terry Pratchett, Terry Pratchett, Neil Gaiman]
}

如图:


flatMap
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。