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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352